From 6e44e2e274d2b3b22b7ac66fb8f84beb703058ab Mon Sep 17 00:00:00 2001 From: Jim Laurie Date: Fri, 12 Jan 2018 15:20:13 +0100 Subject: [PATCH] Add provider connection --- .../environments/development/request.json | 2 +- .../config/functions/bootstrap.js | 39 +++++ .../config/grant.json | 27 +++ .../config/routes.json | 9 + .../controllers/Auth.js | 7 +- .../middlewares/provider/index.js | 33 ++++ .../middlewares/users-permissions/index.js | 7 + .../models/User.settings.json | 3 +- .../package.json | 4 + .../services/Providers.js | 158 ++++++++++++++++++ 10 files changed, 284 insertions(+), 5 deletions(-) create mode 100644 packages/strapi-plugin-users-permissions/config/grant.json create mode 100644 packages/strapi-plugin-users-permissions/middlewares/provider/index.js create mode 100644 packages/strapi-plugin-users-permissions/services/Providers.js diff --git a/packages/strapi-generate-new/files/config/environments/development/request.json b/packages/strapi-generate-new/files/config/environments/development/request.json index 2c18a3099b..b826f5ac0d 100755 --- a/packages/strapi-generate-new/files/config/environments/development/request.json +++ b/packages/strapi-generate-new/files/config/environments/development/request.json @@ -1,6 +1,6 @@ { "session": { - "enabled": false, + "enabled": true, "client": "cookie", "key": "strapi.sid", "prefix": "strapi:sess:", diff --git a/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js b/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js index e72719317b..5b38aa6e55 100644 --- a/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js +++ b/packages/strapi-plugin-users-permissions/config/functions/bootstrap.js @@ -27,6 +27,45 @@ module.exports = cb => { } } + if (!_.get(strapi.plugins['users-permissions'], 'config.grant')) { + try { + const jwtSecret = uuid(); + fs.writeFileSync(path.join(strapi.config.appPath, 'plugins', 'users-permissions', 'config', 'grant.json'), JSON.stringify({ + grant: { + facebook: { + key: '', + secret: '', + callback: '/auth/facebook/callback', + scope: ['email'] + }, + google: { + key: '', + secret: '', + callback: '/auth/google/callback', + scope: ['email'] + }, + github: { + key: '', + secret: '', + callback: '/auth/github/callback' + }, + linkedin2: { + key: '', + secret: '', + callback: '/auth/linkedin2/callback', + custom_params: { + 'state': '' + } + } + } + }, null, 2), 'utf8'); + + _.set(strapi.plugins['users-permissions'], 'config.grant', grant); + } catch(err) { + strapi.log.error(err); + } + } + strapi.plugins['users-permissions'].services.userspermissions.syncSchema(() => { strapi.plugins['users-permissions'].services.userspermissions.updatePermissions(cb); }); diff --git a/packages/strapi-plugin-users-permissions/config/grant.json b/packages/strapi-plugin-users-permissions/config/grant.json new file mode 100644 index 0000000000..7228ff50b1 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/config/grant.json @@ -0,0 +1,27 @@ +{ + "grant": { + "facebook": { + "key": "", + "secret": "", + "callback": "/auth/facebook/callback", + "scope": ["email"] + }, + "google": { + "key": "", + "secret": "", + "callback": "/auth/google/callback", + "scope": ["email"] + }, + "github": { + "key": "", + "secret": "", + "callback": "/auth/github/callback" + }, + "linkedin2": { + "key": "", + "secret": "", + "callback": "/auth/linkedin2/callback", + "custom_params": {"state": ""} + } + } +} diff --git a/packages/strapi-plugin-users-permissions/config/routes.json b/packages/strapi-plugin-users-permissions/config/routes.json index 47f79bbffd..95b5c0e1ee 100644 --- a/packages/strapi-plugin-users-permissions/config/routes.json +++ b/packages/strapi-plugin-users-permissions/config/routes.json @@ -116,6 +116,15 @@ "prefix": "" } }, + { + "method": "GET", + "path": "/auth/:provider/callback", + "handler": "Auth.callback", + "config": { + "policies": [], + "prefix": "" + } + }, { "method": "POST", "path": "/auth/forgot-password", diff --git a/packages/strapi-plugin-users-permissions/controllers/Auth.js b/packages/strapi-plugin-users-permissions/controllers/Auth.js index 5ab1d66e73..896f125937 100644 --- a/packages/strapi-plugin-users-permissions/controllers/Auth.js +++ b/packages/strapi-plugin-users-permissions/controllers/Auth.js @@ -62,9 +62,12 @@ module.exports = { } } else { // Connect the user thanks to the third-party provider. - const user = await strapi.api.user.services.grant.connect(provider, access_token); + const user = await strapi.plugins['users-permissions'].services.providers.connect(provider, access_token); - ctx.redirect(strapi.config.frontendUrl || strapi.config.url + '?jwt=' + strapi.api.user.services.jwt.issue(user) + '&user=' + JSON.stringify(user)); + ctx.send({ + jwt: strapi.plugins['users-permissions'].services.jwt.issue(user), + user: _.omit(user.toJSON ? user.toJSON() : user, ['password', 'resetPasswordToken']) + }); } }, diff --git a/packages/strapi-plugin-users-permissions/middlewares/provider/index.js b/packages/strapi-plugin-users-permissions/middlewares/provider/index.js new file mode 100644 index 0000000000..25b518f413 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/middlewares/provider/index.js @@ -0,0 +1,33 @@ +'use strict'; + +/** + * Module dependencies + */ + +// Public node modules. +const _ = require('lodash'); +const Grant = require('grant-koa'); +const mount = require('koa-mount'); + +module.exports = strapi => { + return { + beforeInitialize: function() { + strapi.config.middleware.load.after.push('provider'); + }, + + initialize: function(cb) { + _.defaultsDeep(strapi.plugins['users-permissions'].config.grant, { + server: { + protocol: 'http', + host: 'localhost:1337' + } + }); + + const grant = new Grant(strapi.plugins['users-permissions'].config.grant); + + strapi.app.use(mount(grant)); + + cb(); + } + }; +}; diff --git a/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js b/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js index 3bf420b536..7e9d41dd19 100644 --- a/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js +++ b/packages/strapi-plugin-users-permissions/middlewares/users-permissions/index.js @@ -1,3 +1,10 @@ +'use strict'; + +/** + * Module dependencies + */ + +// Public node modules. const _ = require('lodash'); module.exports = strapi => { diff --git a/packages/strapi-plugin-users-permissions/models/User.settings.json b/packages/strapi-plugin-users-permissions/models/User.settings.json index bbd8bb545f..6ae2794ddf 100644 --- a/packages/strapi-plugin-users-permissions/models/User.settings.json +++ b/packages/strapi-plugin-users-permissions/models/User.settings.json @@ -26,8 +26,7 @@ "password": { "type": "password", "minLength": 6, - "configurable": false, - "required": true + "configurable": false }, "resetPasswordToken": { "type": "string", diff --git a/packages/strapi-plugin-users-permissions/package.json b/packages/strapi-plugin-users-permissions/package.json index 4fc8ead557..1fc45890f5 100644 --- a/packages/strapi-plugin-users-permissions/package.json +++ b/packages/strapi-plugin-users-permissions/package.json @@ -25,7 +25,11 @@ }, "dependencies": { "bcryptjs": "^2.4.3", + "grant-koa": "^3.8.1", "jsonwebtoken": "^8.1.0", + "koa": "^2.1.0", + "koa-mount": "^3.0.0", + "purest": "^2.0.1", "request": "^2.83.0", "uuid": "^3.1.0" }, diff --git a/packages/strapi-plugin-users-permissions/services/Providers.js b/packages/strapi-plugin-users-permissions/services/Providers.js new file mode 100644 index 0000000000..5d3d3a2219 --- /dev/null +++ b/packages/strapi-plugin-users-permissions/services/Providers.js @@ -0,0 +1,158 @@ +'use strict'; + +/** + * Module dependencies. + */ + +// Public node modules. +const _ = require('lodash'); + +// Purest strategies. +const Purest = require('purest'); + +const facebook = new Purest({ + provider: 'facebook' +}); + +const github = new Purest({ + provider: 'github', + defaults: { + headers: { + 'user-agent': 'strapi' + } + } +}); + +const google = new Purest({ + provider: 'google' +}); + +const linkedin = new Purest({ + provider: 'linkedin' +}); + +/** + * Connect thanks to a third-party provider. + * + * + * @param {String} provider + * @param {String} access_token + * + * @return {*} + */ + +exports.connect = (provider, access_token) => { + return new Promise((resolve, reject) => { + if (!access_token) { + reject({ + message: 'No access_token.' + }); + } else { + // Get the profile. + getProfile(provider, access_token, (err, profile) => { + if (err) { + reject(err); + } else { + // We need at least the mail. + if (!profile.email) { + reject({ + message: 'Email was not available.' + }); + } else { + strapi.query('user', 'users-permissions').findOne({email: profile.email}) + .then(user => { + if (!user) { + // Create the new user. + const params = _.assign(profile, { + provider: provider + }); + + strapi.query('user', 'users-permissions').create(params) + .then(user => { + resolve(user); + }) + .catch(err => { + reject(err); + }); + } else { + resolve(user); + } + }) + .catch(err => { + reject(err); + }); + } + } + }); + } + }); +}; + +/** + * Helper to get profiles + * + * @param {String} provider + * @param {Function} callback + */ + +const getProfile = (provider, access_token, callback) => { + let fields; + switch (provider) { + case 'facebook': + facebook.query().get('me?fields=name,email').auth(access_token).request((err, res, body) => { + if (err) { + callback(err); + } else { + callback(null, { + username: body.name, + email: body.email + }); + } + }); + break; + case 'google': + google.query('plus').get('people/me').auth(access_token).request((err, res, body) => { + if (err) { + callback(err); + } else { + callback(null, { + username: body.displayName, + email: body.emails[0].value + }); + } + }); + break; + case 'github': + github.query().get('user').auth(access_token).request((err, res, body) => { + if (err) { + callback(err); + } else { + callback(null, { + username: body.login, + email: body.email + }); + } + }); + break; + case 'linkedin2': + fields = [ + 'public-profile-url', 'email-address' + ]; + linkedin.query().select('people/~:(' + fields.join() + ')?format=json').auth(access_token).request((err, res, body) => { + if (err) { + callback(err); + } else { + callback(null, { + username: substr(body.publicProfileUrl.lastIndexOf('/') + 1), + email: body.emailAddress + }); + } + }); + break; + default: + callback({ + message: 'Unknown provider.' + }); + break; + } +}