@strapi/plugin-users-permissions 4.0.0-next.1 → 4.0.0-next.13

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.
Files changed (74) hide show
  1. package/admin/src/index.js +31 -28
  2. package/admin/src/pages/AdvancedSettings/index.js +14 -2
  3. package/admin/src/pages/AdvancedSettings/utils/form.js +2 -2
  4. package/admin/src/pages/EmailTemplates/index.js +8 -1
  5. package/admin/src/pages/Providers/index.js +8 -1
  6. package/admin/src/pages/Roles/CreatePage/index.js +1 -1
  7. package/admin/src/pages/Roles/EditPage/index.js +2 -2
  8. package/admin/src/pages/Roles/ListPage/index.js +1 -1
  9. package/admin/src/pages/Roles/index.js +14 -8
  10. package/admin/src/permissions.js +12 -14
  11. package/admin/src/translations/en.json +4 -0
  12. package/admin/src/translations/zh-Hans.json +26 -7
  13. package/documentation/1.0.0/overrides/users-permissions-Role.json +6 -6
  14. package/package.json +8 -6
  15. package/{config/functions/bootstrap.js → server/bootstrap/index.js} +9 -18
  16. package/{config → server/bootstrap}/users-permissions-actions.js +0 -0
  17. package/server/config.js +23 -0
  18. package/server/content-types/index.js +11 -0
  19. package/server/content-types/permission/index.js +31 -0
  20. package/server/content-types/role/index.js +48 -0
  21. package/server/content-types/user/index.js +72 -0
  22. package/{models/User.config.js → server/content-types/user/schema-config.js} +0 -0
  23. package/{controllers → server/controllers}/auth.js +63 -77
  24. package/server/controllers/index.js +15 -0
  25. package/server/controllers/permissions.js +26 -0
  26. package/server/controllers/role.js +77 -0
  27. package/server/controllers/settings.js +84 -0
  28. package/{controllers → server/controllers}/user/admin.js +26 -42
  29. package/{controllers → server/controllers}/user/api.js +11 -27
  30. package/{controllers → server/controllers}/user.js +2 -18
  31. package/{controllers → server/controllers}/validation/email-template.js +0 -0
  32. package/server/index.js +21 -0
  33. package/server/policies/index.js +7 -0
  34. package/{config → server}/policies/rateLimit.js +4 -8
  35. package/server/register.js +7 -0
  36. package/server/routes/admin/index.js +10 -0
  37. package/server/routes/admin/permissions.js +20 -0
  38. package/server/routes/admin/role.js +79 -0
  39. package/server/routes/admin/settings.js +95 -0
  40. package/server/routes/content-api/auth.js +73 -0
  41. package/server/routes/content-api/index.js +11 -0
  42. package/server/routes/content-api/permissions.js +9 -0
  43. package/server/routes/content-api/role.js +29 -0
  44. package/server/routes/content-api/user.js +61 -0
  45. package/server/routes/index.js +6 -0
  46. package/{config → server}/schema.graphql.js +96 -63
  47. package/server/services/index.js +15 -0
  48. package/{services → server/services}/jwt.js +12 -14
  49. package/server/services/providers.js +592 -0
  50. package/server/services/role.js +182 -0
  51. package/{services → server/services}/user.js +31 -34
  52. package/server/services/users-permissions.js +222 -0
  53. package/server/strategies/users-permissions.js +122 -0
  54. package/{utils → server/utils}/index.d.ts +6 -1
  55. package/server/utils/index.js +9 -0
  56. package/strapi-server.js +3 -0
  57. package/config/layout.js +0 -10
  58. package/config/policies/isAuthenticated.js +0 -9
  59. package/config/policies/permissions.js +0 -94
  60. package/config/request.json +0 -6
  61. package/config/routes.json +0 -381
  62. package/config/security.json +0 -5
  63. package/controllers/users-permissions.js +0 -271
  64. package/middlewares/users-permissions/defaults.json +0 -5
  65. package/middlewares/users-permissions/index.js +0 -40
  66. package/models/Permission.js +0 -7
  67. package/models/Permission.settings.json +0 -45
  68. package/models/Role.js +0 -7
  69. package/models/Role.settings.json +0 -43
  70. package/models/User.js +0 -7
  71. package/models/User.settings.json +0 -63
  72. package/services/providers.js +0 -598
  73. package/services/users-permissions.js +0 -430
  74. package/utils/index.js +0 -11
@@ -0,0 +1,592 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Module dependencies.
5
+ */
6
+
7
+ // Public node modules.
8
+ const _ = require('lodash');
9
+ const jwt = require('jsonwebtoken');
10
+
11
+ const { getAbsoluteServerUrl } = require('@strapi/utils');
12
+
13
+ module.exports = ({ strapi }) => {
14
+ // lazy load heavy dependencies
15
+ const request = require('request');
16
+ // Purest strategies.
17
+ const purest = require('purest')({ request });
18
+ const purestConfig = require('@purest/providers');
19
+
20
+ /**
21
+ * Helper to get profiles
22
+ *
23
+ * @param {String} provider
24
+ * @param {Function} callback
25
+ */
26
+
27
+ const getProfile = async (provider, query, callback) => {
28
+ const access_token = query.access_token || query.code || query.oauth_token;
29
+
30
+ const grant = await strapi
31
+ .store({ type: 'plugin', name: 'users-permissions', key: 'grant' })
32
+ .get();
33
+
34
+ switch (provider) {
35
+ case 'discord': {
36
+ const discord = purest({
37
+ provider: 'discord',
38
+ config: {
39
+ discord: {
40
+ 'https://discordapp.com/api/': {
41
+ __domain: {
42
+ auth: {
43
+ auth: { bearer: '[0]' },
44
+ },
45
+ },
46
+ '{endpoint}': {
47
+ __path: {
48
+ alias: '__default',
49
+ },
50
+ },
51
+ },
52
+ },
53
+ },
54
+ });
55
+ discord
56
+ .query()
57
+ .get('users/@me')
58
+ .auth(access_token)
59
+ .request((err, res, body) => {
60
+ if (err) {
61
+ callback(err);
62
+ } else {
63
+ // Combine username and discriminator because discord username is not unique
64
+ var username = `${body.username}#${body.discriminator}`;
65
+ callback(null, {
66
+ username,
67
+ email: body.email,
68
+ });
69
+ }
70
+ });
71
+ break;
72
+ }
73
+ case 'cognito': {
74
+ // get the id_token
75
+ const idToken = query.id_token;
76
+ // decode the jwt token
77
+ const tokenPayload = jwt.decode(idToken);
78
+ if (!tokenPayload) {
79
+ callback(new Error('unable to decode jwt token'));
80
+ } else {
81
+ callback(null, {
82
+ username: tokenPayload['cognito:username'],
83
+ email: tokenPayload.email,
84
+ });
85
+ }
86
+ break;
87
+ }
88
+ case 'facebook': {
89
+ const facebook = purest({
90
+ provider: 'facebook',
91
+ config: purestConfig,
92
+ });
93
+
94
+ facebook
95
+ .query()
96
+ .get('me?fields=name,email')
97
+ .auth(access_token)
98
+ .request((err, res, body) => {
99
+ if (err) {
100
+ callback(err);
101
+ } else {
102
+ callback(null, {
103
+ username: body.name,
104
+ email: body.email,
105
+ });
106
+ }
107
+ });
108
+ break;
109
+ }
110
+ case 'google': {
111
+ const google = purest({ provider: 'google', config: purestConfig });
112
+
113
+ google
114
+ .query('oauth')
115
+ .get('tokeninfo')
116
+ .qs({ access_token })
117
+ .request((err, res, body) => {
118
+ if (err) {
119
+ callback(err);
120
+ } else {
121
+ callback(null, {
122
+ username: body.email.split('@')[0],
123
+ email: body.email,
124
+ });
125
+ }
126
+ });
127
+ break;
128
+ }
129
+ case 'github': {
130
+ const github = purest({
131
+ provider: 'github',
132
+ config: purestConfig,
133
+ defaults: {
134
+ headers: {
135
+ 'user-agent': 'strapi',
136
+ },
137
+ },
138
+ });
139
+
140
+ github
141
+ .query()
142
+ .get('user')
143
+ .auth(access_token)
144
+ .request((err, res, userbody) => {
145
+ if (err) {
146
+ return callback(err);
147
+ }
148
+
149
+ // This is the public email on the github profile
150
+ if (userbody.email) {
151
+ return callback(null, {
152
+ username: userbody.login,
153
+ email: userbody.email,
154
+ });
155
+ }
156
+
157
+ // Get the email with Github's user/emails API
158
+ github
159
+ .query()
160
+ .get('user/emails')
161
+ .auth(access_token)
162
+ .request((err, res, emailsbody) => {
163
+ if (err) {
164
+ return callback(err);
165
+ }
166
+
167
+ return callback(null, {
168
+ username: userbody.login,
169
+ email: Array.isArray(emailsbody)
170
+ ? emailsbody.find(email => email.primary === true).email
171
+ : null,
172
+ });
173
+ });
174
+ });
175
+ break;
176
+ }
177
+ case 'microsoft': {
178
+ const microsoft = purest({
179
+ provider: 'microsoft',
180
+ config: purestConfig,
181
+ });
182
+
183
+ microsoft
184
+ .query()
185
+ .get('me')
186
+ .auth(access_token)
187
+ .request((err, res, body) => {
188
+ if (err) {
189
+ callback(err);
190
+ } else {
191
+ callback(null, {
192
+ username: body.userPrincipalName,
193
+ email: body.userPrincipalName,
194
+ });
195
+ }
196
+ });
197
+ break;
198
+ }
199
+ case 'twitter': {
200
+ const twitter = purest({
201
+ provider: 'twitter',
202
+ config: purestConfig,
203
+ key: grant.twitter.key,
204
+ secret: grant.twitter.secret,
205
+ });
206
+
207
+ twitter
208
+ .query()
209
+ .get('account/verify_credentials')
210
+ .auth(access_token, query.access_secret)
211
+ .qs({ screen_name: query['raw[screen_name]'], include_email: 'true' })
212
+ .request((err, res, body) => {
213
+ if (err) {
214
+ callback(err);
215
+ } else {
216
+ callback(null, {
217
+ username: body.screen_name,
218
+ email: body.email,
219
+ });
220
+ }
221
+ });
222
+ break;
223
+ }
224
+ case 'instagram': {
225
+ const instagram = purest({
226
+ provider: 'instagram',
227
+ key: grant.instagram.key,
228
+ secret: grant.instagram.secret,
229
+ config: purestConfig,
230
+ });
231
+
232
+ instagram
233
+ .query()
234
+ .get('me')
235
+ .qs({ access_token, fields: 'id,username' })
236
+ .request((err, res, body) => {
237
+ if (err) {
238
+ callback(err);
239
+ } else {
240
+ callback(null, {
241
+ username: body.username,
242
+ email: `${body.username}@strapi.io`, // dummy email as Instagram does not provide user email
243
+ });
244
+ }
245
+ });
246
+ break;
247
+ }
248
+ case 'vk': {
249
+ const vk = purest({
250
+ provider: 'vk',
251
+ config: purestConfig,
252
+ });
253
+
254
+ vk.query()
255
+ .get('users.get')
256
+ .qs({ access_token, id: query.raw.user_id, v: '5.122' })
257
+ .request((err, res, body) => {
258
+ if (err) {
259
+ callback(err);
260
+ } else {
261
+ callback(null, {
262
+ username: `${body.response[0].last_name} ${body.response[0].first_name}`,
263
+ email: query.raw.email,
264
+ });
265
+ }
266
+ });
267
+ break;
268
+ }
269
+ case 'twitch': {
270
+ const twitch = purest({
271
+ provider: 'twitch',
272
+ config: {
273
+ twitch: {
274
+ 'https://api.twitch.tv': {
275
+ __domain: {
276
+ auth: {
277
+ headers: {
278
+ Authorization: 'Bearer [0]',
279
+ 'Client-ID': '[1]',
280
+ },
281
+ },
282
+ },
283
+ 'helix/{endpoint}': {
284
+ __path: {
285
+ alias: '__default',
286
+ },
287
+ },
288
+ 'oauth2/{endpoint}': {
289
+ __path: {
290
+ alias: 'oauth',
291
+ },
292
+ },
293
+ },
294
+ },
295
+ },
296
+ });
297
+
298
+ twitch
299
+ .get('users')
300
+ .auth(access_token, grant.twitch.key)
301
+ .request((err, res, body) => {
302
+ if (err) {
303
+ callback(err);
304
+ } else {
305
+ callback(null, {
306
+ username: body.data[0].login,
307
+ email: body.data[0].email,
308
+ });
309
+ }
310
+ });
311
+ break;
312
+ }
313
+ case 'linkedin': {
314
+ const linkedIn = purest({
315
+ provider: 'linkedin',
316
+ config: {
317
+ linkedin: {
318
+ 'https://api.linkedin.com': {
319
+ __domain: {
320
+ auth: [{ auth: { bearer: '[0]' } }],
321
+ },
322
+ '[version]/{endpoint}': {
323
+ __path: {
324
+ alias: '__default',
325
+ version: 'v2',
326
+ },
327
+ },
328
+ },
329
+ },
330
+ },
331
+ });
332
+ try {
333
+ const getDetailsRequest = () => {
334
+ return new Promise((resolve, reject) => {
335
+ linkedIn
336
+ .query()
337
+ .get('me')
338
+ .auth(access_token)
339
+ .request((err, res, body) => {
340
+ if (err) {
341
+ return reject(err);
342
+ }
343
+ resolve(body);
344
+ });
345
+ });
346
+ };
347
+
348
+ const getEmailRequest = () => {
349
+ return new Promise((resolve, reject) => {
350
+ linkedIn
351
+ .query()
352
+ .get('emailAddress?q=members&projection=(elements*(handle~))')
353
+ .auth(access_token)
354
+ .request((err, res, body) => {
355
+ if (err) {
356
+ return reject(err);
357
+ }
358
+ resolve(body);
359
+ });
360
+ });
361
+ };
362
+
363
+ const { localizedFirstName } = await getDetailsRequest();
364
+ const { elements } = await getEmailRequest();
365
+ const email = elements[0]['handle~'];
366
+
367
+ callback(null, {
368
+ username: localizedFirstName,
369
+ email: email.emailAddress,
370
+ });
371
+ } catch (err) {
372
+ callback(err);
373
+ }
374
+ break;
375
+ }
376
+ case 'reddit': {
377
+ const reddit = purest({
378
+ provider: 'reddit',
379
+ config: purestConfig,
380
+ defaults: {
381
+ headers: {
382
+ 'user-agent': 'strapi',
383
+ },
384
+ },
385
+ });
386
+
387
+ reddit
388
+ .query('auth')
389
+ .get('me')
390
+ .auth(access_token)
391
+ .request((err, res, body) => {
392
+ if (err) {
393
+ callback(err);
394
+ } else {
395
+ callback(null, {
396
+ username: body.name,
397
+ email: `${body.name}@strapi.io`, // dummy email as Reddit does not provide user email
398
+ });
399
+ }
400
+ });
401
+ break;
402
+ }
403
+ case 'auth0': {
404
+ const purestAuth0Conf = {};
405
+ purestAuth0Conf[`https://${grant.auth0.subdomain}.auth0.com`] = {
406
+ __domain: {
407
+ auth: {
408
+ auth: { bearer: '[0]' },
409
+ },
410
+ },
411
+ '{endpoint}': {
412
+ __path: {
413
+ alias: '__default',
414
+ },
415
+ },
416
+ };
417
+ const auth0 = purest({
418
+ provider: 'auth0',
419
+ config: {
420
+ auth0: purestAuth0Conf,
421
+ },
422
+ });
423
+
424
+ auth0
425
+ .get('userinfo')
426
+ .auth(access_token)
427
+ .request((err, res, body) => {
428
+ if (err) {
429
+ callback(err);
430
+ } else {
431
+ const username =
432
+ body.username || body.nickname || body.name || body.email.split('@')[0];
433
+ const email = body.email || `${username.replace(/\s+/g, '.')}@strapi.io`;
434
+
435
+ callback(null, {
436
+ username,
437
+ email,
438
+ });
439
+ }
440
+ });
441
+ break;
442
+ }
443
+ case 'cas': {
444
+ const provider_url = 'https://' + _.get(grant['cas'], 'subdomain');
445
+ const cas = purest({
446
+ provider: 'cas',
447
+ config: {
448
+ cas: {
449
+ [provider_url]: {
450
+ __domain: {
451
+ auth: {
452
+ auth: { bearer: '[0]' },
453
+ },
454
+ },
455
+ '{endpoint}': {
456
+ __path: {
457
+ alias: '__default',
458
+ },
459
+ },
460
+ },
461
+ },
462
+ },
463
+ });
464
+ cas
465
+ .query()
466
+ .get('oidc/profile')
467
+ .auth(access_token)
468
+ .request((err, res, body) => {
469
+ if (err) {
470
+ callback(err);
471
+ } else {
472
+ // CAS attribute may be in body.attributes or "FLAT", depending on CAS config
473
+ const username = body.attributes
474
+ ? body.attributes.strapiusername || body.id || body.sub
475
+ : body.strapiusername || body.id || body.sub;
476
+ const email = body.attributes
477
+ ? body.attributes.strapiemail || body.attributes.email
478
+ : body.strapiemail || body.email;
479
+ if (!username || !email) {
480
+ strapi.log.warn(
481
+ 'CAS Response Body did not contain required attributes: ' + JSON.stringify(body)
482
+ );
483
+ }
484
+ callback(null, {
485
+ username,
486
+ email,
487
+ });
488
+ }
489
+ });
490
+ break;
491
+ }
492
+ default:
493
+ callback(new Error('Unknown provider.'));
494
+ break;
495
+ }
496
+ };
497
+
498
+ /**
499
+ * Connect thanks to a third-party provider.
500
+ *
501
+ *
502
+ * @param {String} provider
503
+ * @param {String} access_token
504
+ *
505
+ * @return {*}
506
+ */
507
+
508
+ const connect = (provider, query) => {
509
+ const access_token = query.access_token || query.code || query.oauth_token;
510
+
511
+ return new Promise((resolve, reject) => {
512
+ if (!access_token) {
513
+ return reject([null, { message: 'No access_token.' }]);
514
+ }
515
+
516
+ // Get the profile.
517
+ getProfile(provider, query, async (err, profile) => {
518
+ if (err) {
519
+ return reject([null, err]);
520
+ }
521
+
522
+ // We need at least the mail.
523
+ if (!profile.email) {
524
+ return reject([null, { message: 'Email was not available.' }]);
525
+ }
526
+
527
+ try {
528
+ const users = await strapi.query('plugin::users-permissions.user').findMany({
529
+ where: { email: profile.email },
530
+ });
531
+
532
+ const advanced = await strapi
533
+ .store({ type: 'plugin', name: 'users-permissions', key: 'advanced' })
534
+ .get();
535
+
536
+ const user = _.find(users, { provider });
537
+
538
+ if (_.isEmpty(user) && !advanced.allow_register) {
539
+ return resolve([
540
+ null,
541
+ [{ messages: [{ id: 'Auth.advanced.allow_register' }] }],
542
+ 'Register action is actually not available.',
543
+ ]);
544
+ }
545
+
546
+ if (!_.isEmpty(user)) {
547
+ return resolve([user, null]);
548
+ }
549
+
550
+ if (
551
+ !_.isEmpty(_.find(users, user => user.provider !== provider)) &&
552
+ advanced.unique_email
553
+ ) {
554
+ return resolve([
555
+ null,
556
+ [{ messages: [{ id: 'Auth.form.error.email.taken' }] }],
557
+ 'Email is already taken.',
558
+ ]);
559
+ }
560
+
561
+ // Retrieve default role.
562
+ const defaultRole = await strapi
563
+ .query('plugin::users-permissions.role')
564
+ .findOne({ where: { type: advanced.default_role } });
565
+
566
+ // Create the new user.
567
+ const params = _.assign(profile, {
568
+ provider,
569
+ role: defaultRole.id,
570
+ confirmed: true,
571
+ });
572
+
573
+ const createdUser = await strapi
574
+ .query('plugin::users-permissions.user')
575
+ .create({ data: params });
576
+
577
+ return resolve([createdUser, null]);
578
+ } catch (err) {
579
+ reject([null, err]);
580
+ }
581
+ });
582
+ });
583
+ };
584
+
585
+ const buildRedirectUri = (provider = '') =>
586
+ `${getAbsoluteServerUrl(strapi.config)}/connect/${provider}/callback`;
587
+
588
+ return {
589
+ connect,
590
+ buildRedirectUri,
591
+ };
592
+ };