Skip to content

Commit

Permalink
feat(users): add apple client side oAuth ✨
Browse files Browse the repository at this point in the history
  • Loading branch information
PierreBrisorgueil committed Sep 16, 2020
1 parent 365922b commit a41c351
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 113 deletions.
99 changes: 50 additions & 49 deletions modules/users/config/strategies/apple.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,66 +14,67 @@ const callbackURL = `${config.api.protocol}://${config.api.host}${
config.api.base
}/auth/apple/callback`;

/**
* @desc function to prepare map callback to user profile
* @param {req}
* @param {accessToken}
* @param {refreshToken}
* @param {profile}
* @param {cb} callback
*/
exports.prepare = 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 _profile = {
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',
providerData,
};
// Save the user OAuth profile
try {
const user = await users.checkOAuthUserProfile(_profile, 'sub', 'apple');
return cb(null, user);
} catch (err) {
return cb(err);
}
};

module.exports = () => {
const apple = config.oAuth.apple ? config.oAuth.apple : null;
// Use google strategy
if (
config.oAuth &&
config.oAuth.apple &&
config.oAuth.apple.clientID &&
config.oAuth.apple.teamID &&
config.oAuth.apple.keyID
apple &&
apple.clientID &&
apple.teamID &&
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,
clientID: apple.clientID,
teamID: apple.teamID,
callbackURL: apple.callbackURL ? apple.callbackURL : callbackURL,
keyID: config.oAuth.apple.keyID,
privateKeyLocation: config.oAuth.apple.privateKeyLocation
? config.oAuth.apple.privateKeyLocation
: null,
privateKeyLocation: apple.privateKeyLocation ? 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);
}
},
(req, a, r, d, p, cb) => this.prepare(req, a, r, d, p, cb),
),
);
}
Expand Down
77 changes: 39 additions & 38 deletions modules/users/config/strategies/google.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,52 @@ const callbackURL = `${config.api.protocol}://${config.api.host}${
config.api.base
}/auth/google/callback`;

/**
* @desc function to prepare map callback to user profile
* @param {accessToken}
* @param {refreshToken}
* @param {profile}
* @param {cb} callback
*/
exports.prepare = async (accessToken, refreshToken, profile, cb) => {
// Set the provider data and include tokens
const providerData = profile._json;
providerData.accessToken = accessToken;
providerData.refreshToken = refreshToken;
// Create the user OAuth profile
const _profile = {
firstName: profile.name.givenName,
lastName: profile.name.familyName,
email: profile.emails[0].value,
avatar: providerData.picture ? providerData.picture : undefined,
provider: 'google',
providerData,
};
// Save the user OAuth profile
try {
const user = await users.checkOAuthUserProfile(_profile, 'sub', 'google');
return cb(null, user);
} catch (err) {
return cb(err);
}
};

/**
* @desc Export oAuth Strategie
*/
module.exports = () => {
const google = config.oAuth.google ? config.oAuth.google : null;
// Use google strategy
if (
config.oAuth &&
config.oAuth.google &&
config.oAuth.google.clientID &&
config.oAuth.google.clientSecret
) {
if (google && google.clientID && google.clientSecret) {
passport.use(
new GoogleStrategy(
{
clientID: config.oAuth.google.clientID,
clientSecret: config.oAuth.google.clientSecret,
callbackURL: config.oAuth.google.callbackURL
? config.oAuth.google.callbackURL
: callbackURL,
clientID: google.clientID,
clientSecret: google.clientSecret,
callbackURL: google.callbackURL ? google.callbackURL : callbackURL,
scope: ['profile', 'email'],
},
async (accessToken, refreshToken, profile, cb) => {
// Set the provider data and include tokens
const providerData = profile._json;
providerData.accessToken = accessToken;
providerData.refreshToken = refreshToken;
// Create the user OAuth profile
const providerUserProfile = {
firstName: profile.name.givenName,
lastName: profile.name.familyName,
email: profile.emails[0].value,
avatar: providerData.picture ? providerData.picture : undefined,
provider: 'google',
sub: providerData.sub,
providerData,
};
// Save the user OAuth profile
try {
const user = await users.saveOAuthUserProfile(
providerUserProfile,
'sub',
'google',
);
return cb(null, user);
} catch (err) {
return cb(err);
}
},
(a, r, p, cb) => this.prepare(a, r, p, cb),
),
);
}
Expand Down
78 changes: 52 additions & 26 deletions modules/users/controllers/users/users.authentication.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,25 +117,51 @@ exports.oauthCall = (req, res, next) => {
* @param {Object} res - Express response object
* @param {Function} next - Express next middleware function
*/
exports.oauthCallback = (req, res, next) => {
exports.oauthCallback = async (req, res, next) => {
const strategy = req.params.strategy;
// app Auth with Strategy managed on client side
if (req.body.strategy === false && req.body.key) {
try {
let user = {
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
providerData: {},
};
user.providerData[req.body.key] = req.body.value;
user = await this.checkOAuthUserProfile(user, req.body.key, strategy);
const token = jwt.sign({ userId: user.id }, config.jwt.secret, {
expiresIn: config.jwt.expiresIn,
});
return res
.status(200)
.cookie('TOKEN', token, { httpOnly: true })
.json({
user,
tokenExpiresIn: Date.now() + config.jwt.expiresIn * 1000,
type: 'sucess',
message: 'oAuth Ok',
});
} catch (err) {
return responses.error(
res,
422,
'Unprocessable Entity',
errors.getMessage(err.details || err),
)(err);
}
}
// classic web oAuth
passport.authenticate(strategy, (err, user) => {
const url = config.cors.origin[0];
if (err) {
res.redirect(
302,
`${
config.cors.origin[0]
}/token?message=Unprocessable%20Entity&error=${JSON.stringify(err)}`,
);
const _err = JSON.stringify(err);
const path = 'token?message=Unprocessable%20Entity';
res.redirect(302, `${url}/${path}&error=${_err}`);
} else if (!user) {
res.redirect(
302,
`${
config.cors.origin[0]
}/token?message=Could%20not%20define%20user%20in%20oAuth&error=${JSON.stringify(
err,
)}`,
);
const _err = JSON.stringify(err);
const path = 'token?message=Could%20not%20define%20user%20in%20oAuth';
res.redirect(302, `${url}/${path}&error=${_err}`);
} else {
const token = jwt.sign({ userId: user.id }, config.jwt.secret, {
expiresIn: config.jwt.expiresIn,
Expand All @@ -152,44 +178,44 @@ exports.oauthCallback = (req, res, next) => {
* @param {Object} providerUserProfile
* @param {Function} done - done
*/
exports.saveOAuthUserProfile = async (userProfile, indentifier, provider) => {
exports.checkOAuthUserProfile = async (profil, key, provider) => {
// check if user exist
try {
const query = {};
query[`providerData.${indentifier}`] = userProfile[indentifier];
query[`providerData.${key}`] = profil.providerData[key];
query.provider = provider;
const search = await UserService.search(query);
if (search.length === 1) return search[0];
} catch (err) {
throw new AppError('saveOAuthUserProfile', {
throw new AppError('checkOAuthUserProfile', {
code: 'SERVICE_ERROR',
details: err,
});
}
// if no, generate
try {
const user = {
firstName: userProfile.firstName,
lastName: userProfile.lastName,
email: userProfile.email,
avatar: userProfile.avatar || '',
provider: userProfile.provider,
providerData: userProfile.providerData || null,
firstName: profil.firstName,
lastName: profil.lastName,
email: profil.email,
avatar: profil.avatar || '',
provider,
providerData: profil.providerData || null,
};
const result = model.getResultFromJoi(
user,
UsersSchema.User,
_.clone(config.joi.validationOptions),
);
if (result && result.error) {
throw new AppError('saveOAuthUserProfile schema validation', {
throw new AppError('checkOAuthUserProfile schema validation', {
code: 'SERVICE_ERROR',
details: result.error,
});
}
return await UserService.create(result.value);
} catch (err) {
throw new AppError('saveOAuthUserProfile', {
throw new AppError('checkOAuthUserProfile', {
code: 'SERVICE_ERROR',
details: err,
});
Expand Down

0 comments on commit a41c351

Please sign in to comment.