@strapi/plugin-users-permissions 4.2.1-alpha.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/pages/AdvancedSettings/utils/layout.js +2 -2
- package/admin/src/translations/en.json +1 -1
- package/admin/src/translations/pl.json +44 -7
- package/documentation/content-api.yaml +813 -0
- package/package.json +4 -4
- package/server/controllers/auth.js +170 -233
- package/server/controllers/user.js +7 -6
- package/server/controllers/validation/auth.js +31 -6
- package/server/register.js +9 -0
- package/server/routes/content-api/user.js +0 -1
- package/server/services/index.js +2 -0
- package/server/services/providers-registry.js +301 -0
- package/server/services/providers.js +62 -70
- package/server/utils/index.d.ts +1 -1
- package/documentation/1.0.0/overrides/users-permissions-Role.json +0 -281
- package/documentation/1.0.0/overrides/users-permissions-User.json +0 -325
- package/server/services/providers-list.js +0 -277
package/server/register.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
2
4
|
|
|
3
5
|
const authStrategy = require('./strategies/users-permissions');
|
|
4
6
|
const sanitizers = require('./utils/sanitize/sanitizers');
|
|
@@ -10,4 +12,11 @@ module.exports = ({ strapi }) => {
|
|
|
10
12
|
if (strapi.plugin('graphql')) {
|
|
11
13
|
require('./graphql')({ strapi });
|
|
12
14
|
}
|
|
15
|
+
|
|
16
|
+
if (strapi.plugin('documentation')) {
|
|
17
|
+
const specPath = path.join(__dirname, '../documentation/content-api.yaml');
|
|
18
|
+
const spec = fs.readFileSync(specPath, 'utf8');
|
|
19
|
+
|
|
20
|
+
strapi.plugin('documentation').service('documentation').registerDoc(spec);
|
|
21
|
+
}
|
|
13
22
|
};
|
package/server/services/index.js
CHANGED
|
@@ -5,10 +5,12 @@ const providers = require('./providers');
|
|
|
5
5
|
const user = require('./user');
|
|
6
6
|
const role = require('./role');
|
|
7
7
|
const usersPermissions = require('./users-permissions');
|
|
8
|
+
const providersRegistry = require('./providers-registry');
|
|
8
9
|
|
|
9
10
|
module.exports = {
|
|
10
11
|
jwt,
|
|
11
12
|
providers,
|
|
13
|
+
'providers-registry': providersRegistry,
|
|
12
14
|
role,
|
|
13
15
|
user,
|
|
14
16
|
'users-permissions': usersPermissions,
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { strict: assert } = require('assert');
|
|
4
|
+
const jwt = require('jsonwebtoken');
|
|
5
|
+
|
|
6
|
+
const getInitialProviders = ({ purest }) => ({
|
|
7
|
+
async discord({ access_token }) {
|
|
8
|
+
const discord = purest({ provider: 'discord' });
|
|
9
|
+
return discord
|
|
10
|
+
.get('users/@me')
|
|
11
|
+
.auth(access_token)
|
|
12
|
+
.request()
|
|
13
|
+
.then(({ body }) => {
|
|
14
|
+
// Combine username and discriminator because discord username is not unique
|
|
15
|
+
var username = `${body.username}#${body.discriminator}`;
|
|
16
|
+
return {
|
|
17
|
+
username,
|
|
18
|
+
email: body.email,
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
async cognito({ query }) {
|
|
23
|
+
// get the id_token
|
|
24
|
+
const idToken = query.id_token;
|
|
25
|
+
// decode the jwt token
|
|
26
|
+
const tokenPayload = jwt.decode(idToken);
|
|
27
|
+
if (!tokenPayload) {
|
|
28
|
+
throw new Error('unable to decode jwt token');
|
|
29
|
+
} else {
|
|
30
|
+
return {
|
|
31
|
+
username: tokenPayload['cognito:username'],
|
|
32
|
+
email: tokenPayload.email,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
async facebook({ access_token }) {
|
|
37
|
+
const facebook = purest({ provider: 'facebook' });
|
|
38
|
+
|
|
39
|
+
return facebook
|
|
40
|
+
.get('me')
|
|
41
|
+
.auth(access_token)
|
|
42
|
+
.qs({ fields: 'name,email' })
|
|
43
|
+
.request()
|
|
44
|
+
.then(({ body }) => ({
|
|
45
|
+
username: body.name,
|
|
46
|
+
email: body.email,
|
|
47
|
+
}));
|
|
48
|
+
},
|
|
49
|
+
async google({ access_token }) {
|
|
50
|
+
const google = purest({ provider: 'google' });
|
|
51
|
+
|
|
52
|
+
return google
|
|
53
|
+
.query('oauth')
|
|
54
|
+
.get('tokeninfo')
|
|
55
|
+
.qs({ access_token })
|
|
56
|
+
.request()
|
|
57
|
+
.then(({ body }) => ({
|
|
58
|
+
username: body.email.split('@')[0],
|
|
59
|
+
email: body.email,
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
async github({ access_token }) {
|
|
63
|
+
const github = purest({
|
|
64
|
+
provider: 'github',
|
|
65
|
+
defaults: {
|
|
66
|
+
headers: {
|
|
67
|
+
'user-agent': 'strapi',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const { body: userBody } = await github
|
|
73
|
+
.get('user')
|
|
74
|
+
.auth(access_token)
|
|
75
|
+
.request();
|
|
76
|
+
|
|
77
|
+
// This is the public email on the github profile
|
|
78
|
+
if (userBody.email) {
|
|
79
|
+
return {
|
|
80
|
+
username: userBody.login,
|
|
81
|
+
email: userBody.email,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Get the email with Github's user/emails API
|
|
85
|
+
const { body: emailBody } = await github
|
|
86
|
+
.get('user/emails')
|
|
87
|
+
.auth(access_token)
|
|
88
|
+
.request();
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
username: userBody.login,
|
|
92
|
+
email: Array.isArray(emailBody)
|
|
93
|
+
? emailBody.find(email => email.primary === true).email
|
|
94
|
+
: null,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
async microsoft({ access_token }) {
|
|
98
|
+
const microsoft = purest({ provider: 'microsoft' });
|
|
99
|
+
|
|
100
|
+
return microsoft
|
|
101
|
+
.get('me')
|
|
102
|
+
.auth(access_token)
|
|
103
|
+
.request()
|
|
104
|
+
.then(({ body }) => ({
|
|
105
|
+
username: body.userPrincipalName,
|
|
106
|
+
email: body.userPrincipalName,
|
|
107
|
+
}));
|
|
108
|
+
},
|
|
109
|
+
async twitter({ access_token, query, providers }) {
|
|
110
|
+
const twitter = purest({
|
|
111
|
+
provider: 'twitter',
|
|
112
|
+
defaults: {
|
|
113
|
+
oauth: {
|
|
114
|
+
consumer_key: providers.twitter.key,
|
|
115
|
+
consumer_secret: providers.twitter.secret,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return twitter
|
|
121
|
+
.get('account/verify_credentials')
|
|
122
|
+
.auth(access_token, query.access_secret)
|
|
123
|
+
.qs({ screen_name: query['raw[screen_name]'], include_email: 'true' })
|
|
124
|
+
.request()
|
|
125
|
+
.then(({ body }) => ({
|
|
126
|
+
username: body.screen_name,
|
|
127
|
+
email: body.email,
|
|
128
|
+
}));
|
|
129
|
+
},
|
|
130
|
+
async instagram({ access_token }) {
|
|
131
|
+
const instagram = purest({ provider: 'instagram' });
|
|
132
|
+
|
|
133
|
+
return instagram
|
|
134
|
+
.get('me')
|
|
135
|
+
.auth(access_token)
|
|
136
|
+
.qs({ fields: 'id,username' })
|
|
137
|
+
.request()
|
|
138
|
+
.then(({ body }) => ({
|
|
139
|
+
username: body.username,
|
|
140
|
+
email: `${body.username}@strapi.io`, // dummy email as Instagram does not provide user email
|
|
141
|
+
}));
|
|
142
|
+
},
|
|
143
|
+
async vk({ access_token, query }) {
|
|
144
|
+
const vk = purest({ provider: 'vk' });
|
|
145
|
+
|
|
146
|
+
return vk
|
|
147
|
+
.get('users.get')
|
|
148
|
+
.auth(access_token)
|
|
149
|
+
.qs({ id: query.raw.user_id, v: '5.122' })
|
|
150
|
+
.request()
|
|
151
|
+
.then(({ body }) => ({
|
|
152
|
+
username: `${body.response[0].last_name} ${body.response[0].first_name}`,
|
|
153
|
+
email: query.raw.email,
|
|
154
|
+
}));
|
|
155
|
+
},
|
|
156
|
+
async twitch({ access_token, providers }) {
|
|
157
|
+
const twitch = purest({
|
|
158
|
+
provider: 'twitch',
|
|
159
|
+
config: {
|
|
160
|
+
twitch: {
|
|
161
|
+
default: {
|
|
162
|
+
origin: 'https://api.twitch.tv',
|
|
163
|
+
path: 'helix/{path}',
|
|
164
|
+
headers: {
|
|
165
|
+
Authorization: 'Bearer {auth}',
|
|
166
|
+
'Client-Id': '{auth}',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return twitch
|
|
174
|
+
.get('users')
|
|
175
|
+
.auth(access_token, providers.twitch.key)
|
|
176
|
+
.request()
|
|
177
|
+
.then(({ body }) => ({
|
|
178
|
+
username: body.data[0].login,
|
|
179
|
+
email: body.data[0].email,
|
|
180
|
+
}));
|
|
181
|
+
},
|
|
182
|
+
async linkedin({ access_token }) {
|
|
183
|
+
const linkedIn = purest({ provider: 'linkedin' });
|
|
184
|
+
const {
|
|
185
|
+
body: { localizedFirstName },
|
|
186
|
+
} = await linkedIn
|
|
187
|
+
.get('me')
|
|
188
|
+
.auth(access_token)
|
|
189
|
+
.request();
|
|
190
|
+
const {
|
|
191
|
+
body: { elements },
|
|
192
|
+
} = await linkedIn
|
|
193
|
+
.get('emailAddress?q=members&projection=(elements*(handle~))')
|
|
194
|
+
.auth(access_token)
|
|
195
|
+
.request();
|
|
196
|
+
|
|
197
|
+
const email = elements[0]['handle~'];
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
username: localizedFirstName,
|
|
201
|
+
email: email.emailAddress,
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
async reddit({ access_token }) {
|
|
205
|
+
const reddit = purest({
|
|
206
|
+
provider: 'reddit',
|
|
207
|
+
config: {
|
|
208
|
+
reddit: {
|
|
209
|
+
default: {
|
|
210
|
+
origin: 'https://oauth.reddit.com',
|
|
211
|
+
path: 'api/{version}/{path}',
|
|
212
|
+
version: 'v1',
|
|
213
|
+
headers: {
|
|
214
|
+
Authorization: 'Bearer {auth}',
|
|
215
|
+
'user-agent': 'strapi',
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return reddit
|
|
223
|
+
.get('me')
|
|
224
|
+
.auth(access_token)
|
|
225
|
+
.request()
|
|
226
|
+
.then(({ body }) => ({
|
|
227
|
+
username: body.name,
|
|
228
|
+
email: `${body.name}@strapi.io`, // dummy email as Reddit does not provide user email
|
|
229
|
+
}));
|
|
230
|
+
},
|
|
231
|
+
async auth0({ access_token, providers }) {
|
|
232
|
+
const auth0 = purest({ provider: 'auth0' });
|
|
233
|
+
|
|
234
|
+
return auth0
|
|
235
|
+
.get('userinfo')
|
|
236
|
+
.subdomain(providers.auth0.subdomain)
|
|
237
|
+
.auth(access_token)
|
|
238
|
+
.request()
|
|
239
|
+
.then(({ body }) => {
|
|
240
|
+
const username = body.username || body.nickname || body.name || body.email.split('@')[0];
|
|
241
|
+
const email = body.email || `${username.replace(/\s+/g, '.')}@strapi.io`;
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
username,
|
|
245
|
+
email,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
async cas({ access_token, providers }) {
|
|
250
|
+
const cas = purest({ provider: 'cas' });
|
|
251
|
+
|
|
252
|
+
return cas
|
|
253
|
+
.get('oidc/profile')
|
|
254
|
+
.subdomain(providers.cas.subdomain)
|
|
255
|
+
.auth(access_token)
|
|
256
|
+
.request()
|
|
257
|
+
.then(({ body }) => {
|
|
258
|
+
// CAS attribute may be in body.attributes or "FLAT", depending on CAS config
|
|
259
|
+
const username = body.attributes
|
|
260
|
+
? body.attributes.strapiusername || body.id || body.sub
|
|
261
|
+
: body.strapiusername || body.id || body.sub;
|
|
262
|
+
const email = body.attributes
|
|
263
|
+
? body.attributes.strapiemail || body.attributes.email
|
|
264
|
+
: body.strapiemail || body.email;
|
|
265
|
+
if (!username || !email) {
|
|
266
|
+
strapi.log.warn(
|
|
267
|
+
'CAS Response Body did not contain required attributes: ' + JSON.stringify(body)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
username,
|
|
272
|
+
email,
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
module.exports = () => {
|
|
279
|
+
const purest = require('purest');
|
|
280
|
+
|
|
281
|
+
const providersCallbacks = getInitialProviders({ purest });
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
register(providerName, provider) {
|
|
285
|
+
assert(typeof providerName === 'string', 'Provider name must be a string');
|
|
286
|
+
assert(typeof provider === 'function', 'Provider callback must be a function');
|
|
287
|
+
|
|
288
|
+
providersCallbacks[providerName] = provider({ purest });
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
async run({ provider, access_token, query, providers }) {
|
|
292
|
+
if (!providersCallbacks[provider]) {
|
|
293
|
+
throw new Error('Unknown provider.');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const providerCb = providersCallbacks[provider];
|
|
297
|
+
|
|
298
|
+
return providerCb({ access_token, query, providers });
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
};
|
|
@@ -9,11 +9,9 @@ const _ = require('lodash');
|
|
|
9
9
|
const urlJoin = require('url-join');
|
|
10
10
|
|
|
11
11
|
const { getAbsoluteServerUrl } = require('@strapi/utils');
|
|
12
|
+
const { getService } = require('../utils');
|
|
12
13
|
|
|
13
14
|
module.exports = ({ strapi }) => {
|
|
14
|
-
// lazy load heavy dependencies
|
|
15
|
-
const providerRequest = require('./providers-list');
|
|
16
|
-
|
|
17
15
|
/**
|
|
18
16
|
* Helper to get profiles
|
|
19
17
|
*
|
|
@@ -27,7 +25,12 @@ module.exports = ({ strapi }) => {
|
|
|
27
25
|
.store({ type: 'plugin', name: 'users-permissions', key: 'grant' })
|
|
28
26
|
.get();
|
|
29
27
|
|
|
30
|
-
return
|
|
28
|
+
return getService('providers-registry').run({
|
|
29
|
+
provider,
|
|
30
|
+
query,
|
|
31
|
+
access_token,
|
|
32
|
+
providers,
|
|
33
|
+
});
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
/**
|
|
@@ -40,75 +43,64 @@ module.exports = ({ strapi }) => {
|
|
|
40
43
|
* @return {*}
|
|
41
44
|
*/
|
|
42
45
|
|
|
43
|
-
const connect = (provider, query) => {
|
|
46
|
+
const connect = async (provider, query) => {
|
|
44
47
|
const access_token = query.access_token || query.code || query.oauth_token;
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const users = await strapi.query('plugin::users-permissions.user').findMany({
|
|
63
|
-
where: { email },
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const advanced = await strapi
|
|
67
|
-
.store({ type: 'plugin', name: 'users-permissions', key: 'advanced' })
|
|
68
|
-
.get();
|
|
69
|
-
|
|
70
|
-
const user = _.find(users, { provider });
|
|
71
|
-
|
|
72
|
-
if (_.isEmpty(user) && !advanced.allow_register) {
|
|
73
|
-
return reject({ message: 'Register action is actually not available.' });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!_.isEmpty(user)) {
|
|
77
|
-
return resolve(user);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
!_.isEmpty(_.find(users, user => user.provider !== provider)) &&
|
|
82
|
-
advanced.unique_email
|
|
83
|
-
) {
|
|
84
|
-
return reject({ message: 'Email is already taken.' });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Retrieve default role.
|
|
88
|
-
const defaultRole = await strapi
|
|
89
|
-
.query('plugin::users-permissions.role')
|
|
90
|
-
.findOne({ where: { type: advanced.default_role } });
|
|
91
|
-
|
|
92
|
-
// Create the new user.
|
|
93
|
-
const params = {
|
|
94
|
-
...profile,
|
|
95
|
-
email, // overwrite with lowercased email
|
|
96
|
-
provider,
|
|
97
|
-
role: defaultRole.id,
|
|
98
|
-
confirmed: true,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const createdUser = await strapi
|
|
102
|
-
.query('plugin::users-permissions.user')
|
|
103
|
-
.create({ data: params });
|
|
104
|
-
|
|
105
|
-
return resolve(createdUser);
|
|
106
|
-
} catch (err) {
|
|
107
|
-
reject(err);
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
.catch(reject);
|
|
49
|
+
if (!access_token) {
|
|
50
|
+
throw new Error('No access_token.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get the profile.
|
|
54
|
+
const profile = await getProfile(provider, query);
|
|
55
|
+
|
|
56
|
+
const email = _.toLower(profile.email);
|
|
57
|
+
|
|
58
|
+
// We need at least the mail.
|
|
59
|
+
if (!email) {
|
|
60
|
+
throw new Error('Email was not available.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const users = await strapi.query('plugin::users-permissions.user').findMany({
|
|
64
|
+
where: { email },
|
|
111
65
|
});
|
|
66
|
+
|
|
67
|
+
const advancedSettings = await strapi
|
|
68
|
+
.store({ type: 'plugin', name: 'users-permissions', key: 'advanced' })
|
|
69
|
+
.get();
|
|
70
|
+
|
|
71
|
+
const user = _.find(users, { provider });
|
|
72
|
+
|
|
73
|
+
if (_.isEmpty(user) && !advancedSettings.allow_register) {
|
|
74
|
+
throw new Error('Register action is actually not available.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!_.isEmpty(user)) {
|
|
78
|
+
return user;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (users.length > 1 && advancedSettings.unique_email) {
|
|
82
|
+
throw new Error('Email is already taken.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Retrieve default role.
|
|
86
|
+
const defaultRole = await strapi
|
|
87
|
+
.query('plugin::users-permissions.role')
|
|
88
|
+
.findOne({ where: { type: advancedSettings.default_role } });
|
|
89
|
+
|
|
90
|
+
// Create the new user.
|
|
91
|
+
const newUser = {
|
|
92
|
+
...profile,
|
|
93
|
+
email, // overwrite with lowercased email
|
|
94
|
+
provider,
|
|
95
|
+
role: defaultRole.id,
|
|
96
|
+
confirmed: true,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const createdUser = await strapi
|
|
100
|
+
.query('plugin::users-permissions.user')
|
|
101
|
+
.create({ data: newUser });
|
|
102
|
+
|
|
103
|
+
return createdUser;
|
|
112
104
|
};
|
|
113
105
|
|
|
114
106
|
const buildRedirectUri = (provider = '') => {
|
package/server/utils/index.d.ts
CHANGED
|
@@ -4,13 +4,13 @@ import * as role from '../services/role';
|
|
|
4
4
|
import * as jwt from '../services/jwt';
|
|
5
5
|
import * as providers from '../services/providers';
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
type S = {
|
|
9
8
|
['users-permissions']: typeof usersPermissions;
|
|
10
9
|
['role']: typeof role;
|
|
11
10
|
user: typeof user;
|
|
12
11
|
jwt: typeof jwt;
|
|
13
12
|
providers: typeof providers;
|
|
13
|
+
['providers-registry']: typeof providers;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export function getService<T extends keyof S>(name: T): ReturnType<S[T]>;
|