600 lines
16 KiB
JavaScript
Raw Normal View History

2018-01-12 15:20:13 +01:00
'use strict';
/**
* Module dependencies.
*/
// Public node modules.
const _ = require('lodash');
2021-09-03 11:11:37 +02:00
const jwt = require('jsonwebtoken');
2021-11-29 10:41:26 +01:00
const urlJoin = require('url-join');
2018-01-12 15:20:13 +01:00
2021-04-29 13:51:12 +02:00
const { getAbsoluteServerUrl } = require('@strapi/utils');
2018-01-12 15:20:13 +01:00
2021-08-20 15:23:02 +02:00
module.exports = ({ strapi }) => {
2021-09-03 11:11:37 +02:00
// lazy load heavy dependencies
const request = require('request');
// Purest strategies.
const purest = require('purest')({ request });
const purestConfig = require('@purest/providers');
2021-08-20 15:23:02 +02:00
/**
* Helper to get profiles
*
* @param {String} provider
* @param {Function} callback
*/
const getProfile = async (provider, query, callback) => {
const access_token = query.access_token || query.code || query.oauth_token;
const providers = await strapi
.store({ type: 'plugin', name: 'users-permissions', key: 'grant' })
2021-08-20 15:23:02 +02:00
.get();
switch (provider) {
case 'discord': {
const discord = purest({
provider: 'discord',
config: {
discord: {
'https://discordapp.com/api/': {
__domain: {
auth: {
auth: { bearer: '[0]' },
},
},
2021-08-20 15:23:02 +02:00
'{endpoint}': {
__path: {
alias: '__default',
},
},
},
},
},
});
2021-08-20 15:23:02 +02:00
discord
.query()
.get('users/@me')
.auth(access_token)
.request((err, res, body) => {
if (err) {
callback(err);
} else {
// Combine username and discriminator because discord username is not unique
var username = `${body.username}#${body.discriminator}`;
callback(null, {
username,
2021-08-20 15:23:02 +02:00
email: body.email,
});
}
});
break;
}
2021-08-20 15:23:02 +02:00
case 'cognito': {
// get the id_token
const idToken = query.id_token;
// decode the jwt token
const tokenPayload = jwt.decode(idToken);
if (!tokenPayload) {
callback(new Error('unable to decode jwt token'));
} else {
callback(null, {
username: tokenPayload['cognito:username'],
email: tokenPayload.email,
});
}
break;
}
case 'facebook': {
const facebook = purest({
provider: 'facebook',
config: purestConfig,
});
2021-08-20 15:23:02 +02:00
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': {
const google = purest({ provider: 'google', config: purestConfig });
google
.query('oauth')
.get('tokeninfo')
.qs({ access_token })
.request((err, res, body) => {
if (err) {
callback(err);
} else {
callback(null, {
username: body.email.split('@')[0],
email: body.email,
});
}
});
break;
}
case 'github': {
const github = purest({
provider: 'github',
config: purestConfig,
defaults: {
headers: {
'user-agent': 'strapi',
},
},
});
2021-08-20 15:23:02 +02:00
github
.query()
.get('user')
.auth(access_token)
.request((err, res, userbody) => {
if (err) {
return callback(err);
}
2021-08-20 15:23:02 +02:00
// This is the public email on the github profile
if (userbody.email) {
return callback(null, {
username: userbody.login,
2021-08-20 15:23:02 +02:00
email: userbody.email,
});
2021-08-20 15:23:02 +02:00
}
2021-08-20 15:23:02 +02:00
// Get the email with Github's user/emails API
github
.query()
.get('user/emails')
.auth(access_token)
.request((err, res, emailsbody) => {
if (err) {
return callback(err);
}
return callback(null, {
username: userbody.login,
email: Array.isArray(emailsbody)
? emailsbody.find(email => email.primary === true).email
2021-08-20 15:23:02 +02:00
: null,
});
});
});
break;
}
case 'microsoft': {
const microsoft = purest({
provider: 'microsoft',
config: purestConfig,
});
2018-01-25 15:04:42 +01:00
2021-08-20 15:23:02 +02:00
microsoft
.query()
.get('me')
.auth(access_token)
.request((err, res, body) => {
if (err) {
callback(err);
} else {
callback(null, {
username: body.userPrincipalName,
email: body.userPrincipalName,
});
}
});
break;
}
case 'twitter': {
const twitter = purest({
provider: 'twitter',
config: purestConfig,
key: providers.twitter.key,
secret: providers.twitter.secret,
});
2019-09-27 16:26:09 +02:00
2021-08-20 15:23:02 +02:00
twitter
.query()
.get('account/verify_credentials')
.auth(access_token, query.access_secret)
.qs({ screen_name: query['raw[screen_name]'], include_email: 'true' })
.request((err, res, body) => {
if (err) {
callback(err);
} else {
callback(null, {
username: body.screen_name,
email: body.email,
});
}
});
break;
}
case 'instagram': {
const instagram = purest({
provider: 'instagram',
key: providers.instagram.key,
secret: providers.instagram.secret,
2021-08-20 15:23:02 +02:00
config: purestConfig,
2019-09-27 16:26:09 +02:00
});
2019-12-06 01:38:06 +03:00
2021-08-20 15:23:02 +02:00
instagram
.query()
.get('me')
.qs({ access_token, fields: 'id,username' })
.request((err, res, body) => {
if (err) {
callback(err);
} else {
callback(null, {
username: body.username,
email: `${body.username}@strapi.io`, // dummy email as Instagram does not provide user email
});
}
});
break;
}
case 'vk': {
const vk = purest({
provider: 'vk',
config: purestConfig,
2019-12-06 01:38:06 +03:00
});
2021-08-20 15:23:02 +02:00
vk.query()
.get('users.get')
.qs({ access_token, id: query.raw.user_id, v: '5.122' })
.request((err, res, body) => {
if (err) {
callback(err);
} else {
callback(null, {
username: `${body.response[0].last_name} ${body.response[0].first_name}`,
email: query.raw.email,
});
}
});
break;
}
case 'twitch': {
const twitch = purest({
provider: 'twitch',
config: {
twitch: {
'https://api.twitch.tv': {
__domain: {
auth: {
headers: {
Authorization: 'Bearer [0]',
'Client-ID': '[1]',
},
},
},
2021-08-20 15:23:02 +02:00
'helix/{endpoint}': {
__path: {
alias: '__default',
},
},
2021-08-20 15:23:02 +02:00
'oauth2/{endpoint}': {
__path: {
alias: 'oauth',
},
},
},
},
},
});
2021-08-20 15:23:02 +02:00
twitch
.get('users')
.auth(access_token, providers.twitch.key)
2021-08-20 15:23:02 +02:00
.request((err, res, body) => {
if (err) {
callback(err);
} else {
callback(null, {
username: body.data[0].login,
email: body.data[0].email,
});
}
});
break;
}
case 'linkedin': {
const linkedIn = purest({
provider: 'linkedin',
config: {
linkedin: {
'https://api.linkedin.com': {
__domain: {
auth: [{ auth: { bearer: '[0]' } }],
},
'[version]/{endpoint}': {
__path: {
alias: '__default',
version: 'v2',
},
},
},
},
},
2021-08-20 15:23:02 +02:00
});
try {
const getDetailsRequest = () => {
return new Promise((resolve, reject) => {
linkedIn
.query()
.get('me')
.auth(access_token)
.request((err, res, body) => {
if (err) {
return reject(err);
}
resolve(body);
});
});
};
const getEmailRequest = () => {
return new Promise((resolve, reject) => {
linkedIn
.query()
.get('emailAddress?q=members&projection=(elements*(handle~))')
.auth(access_token)
.request((err, res, body) => {
if (err) {
return reject(err);
}
resolve(body);
});
});
};
const { localizedFirstName } = await getDetailsRequest();
const { elements } = await getEmailRequest();
const email = elements[0]['handle~'];
callback(null, {
username: localizedFirstName,
email: email.emailAddress,
});
2021-08-20 15:23:02 +02:00
} catch (err) {
callback(err);
}
break;
}
case 'reddit': {
const reddit = purest({
provider: 'reddit',
config: purestConfig,
defaults: {
headers: {
'user-agent': 'strapi',
},
},
});
2021-08-20 15:23:02 +02:00
reddit
.query('auth')
.get('me')
.auth(access_token)
.request((err, res, body) => {
if (err) {
callback(err);
} else {
callback(null, {
username: body.name,
email: `${body.name}@strapi.io`, // dummy email as Reddit does not provide user email
});
2021-08-20 15:23:02 +02:00
}
});
2021-08-20 15:23:02 +02:00
break;
}
2021-08-20 15:23:02 +02:00
case 'auth0': {
const purestAuth0Conf = {};
purestAuth0Conf[`https://${providers.auth0.subdomain}.auth0.com`] = {
2021-08-20 15:23:02 +02:00
__domain: {
auth: {
auth: { bearer: '[0]' },
},
},
2021-08-20 15:23:02 +02:00
'{endpoint}': {
__path: {
alias: '__default',
},
},
2021-08-20 15:23:02 +02:00
};
const auth0 = purest({
provider: 'auth0',
config: {
auth0: purestAuth0Conf,
},
});
2021-08-20 15:23:02 +02:00
auth0
.get('userinfo')
.auth(access_token)
.request((err, res, body) => {
if (err) {
callback(err);
} else {
const username =
body.username || body.nickname || body.name || body.email.split('@')[0];
const email = body.email || `${username.replace(/\s+/g, '.')}@strapi.io`;
callback(null, {
username,
email,
});
}
});
break;
}
case 'cas': {
const provider_url = 'https://' + _.get(providers.cas, 'subdomain');
2021-08-20 15:23:02 +02:00
const cas = purest({
provider: 'cas',
config: {
cas: {
[provider_url]: {
__domain: {
auth: {
auth: { bearer: '[0]' },
},
},
2021-08-20 15:23:02 +02:00
'{endpoint}': {
__path: {
alias: '__default',
},
},
},
},
},
});
2021-08-20 15:23:02 +02:00
cas
.query()
.get('oidc/profile')
.auth(access_token)
.request((err, res, body) => {
if (err) {
callback(err);
} else {
// CAS attribute may be in body.attributes or "FLAT", depending on CAS config
const username = body.attributes
? body.attributes.strapiusername || body.id || body.sub
: body.strapiusername || body.id || body.sub;
const email = body.attributes
? body.attributes.strapiemail || body.attributes.email
: body.strapiemail || body.email;
if (!username || !email) {
strapi.log.warn(
'CAS Response Body did not contain required attributes: ' + JSON.stringify(body)
);
}
callback(null, {
username,
email,
});
}
});
break;
}
default:
callback(new Error('Unknown provider.'));
break;
}
2021-08-20 15:23:02 +02:00
};
/**
* Connect thanks to a third-party provider.
*
*
* @param {String} provider
* @param {String} access_token
*
* @return {*}
*/
const connect = (provider, query) => {
const access_token = query.access_token || query.code || query.oauth_token;
return new Promise((resolve, reject) => {
if (!access_token) {
return reject([null, { message: 'No access_token.' }]);
}
// Get the profile.
getProfile(provider, query, async (err, profile) => {
if (err) {
return reject([null, err]);
}
const email = _.toLower(profile.email);
2021-08-20 15:23:02 +02:00
// We need at least the mail.
if (!email) {
2021-08-20 15:23:02 +02:00
return reject([null, { message: 'Email was not available.' }]);
}
try {
const users = await strapi.query('plugin::users-permissions.user').findMany({
where: { email },
2021-08-20 15:23:02 +02:00
});
const advanced = await strapi
.store({ type: 'plugin', name: 'users-permissions', key: 'advanced' })
2021-08-20 15:23:02 +02:00
.get();
const user = _.find(users, { provider });
if (_.isEmpty(user) && !advanced.allow_register) {
return resolve([
null,
[{ messages: [{ id: 'Auth.advanced.allow_register' }] }],
'Register action is actually not available.',
]);
}
2021-08-20 15:23:02 +02:00
if (!_.isEmpty(user)) {
return resolve([user, null]);
}
if (
!_.isEmpty(_.find(users, user => user.provider !== provider)) &&
2021-08-20 15:23:02 +02:00
advanced.unique_email
) {
return resolve([
null,
[{ messages: [{ id: 'Auth.form.error.email.taken' }] }],
'Email is already taken.',
]);
}
// Retrieve default role.
const defaultRole = await strapi
.query('plugin::users-permissions.role')
.findOne({ where: { type: advanced.default_role } });
// Create the new user.
const params = {
...profile,
email, // overwrite with lowercased email
provider,
2021-08-20 15:23:02 +02:00
role: defaultRole.id,
confirmed: true,
};
2021-08-20 15:23:02 +02:00
const createdUser = await strapi
.query('plugin::users-permissions.user')
.create({ data: params });
2021-08-20 15:23:02 +02:00
return resolve([createdUser, null]);
} catch (err) {
reject([null, err]);
}
});
});
};
2021-10-26 16:51:29 +02:00
const buildRedirectUri = (provider = '') => {
const apiPrefix = strapi.config.get('api.rest.prefix');
2021-11-29 10:41:26 +01:00
return urlJoin(getAbsoluteServerUrl(strapi.config), apiPrefix, 'connect', provider, 'callback');
2021-10-26 16:51:29 +02:00
};
2021-08-20 15:23:02 +02:00
return {
connect,
buildRedirectUri,
};
};