@strapi/plugin-users-permissions 4.0.0-beta.2 → 4.0.0-beta.20

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 (46) hide show
  1. package/admin/src/components/BoundRoute/index.js +23 -27
  2. package/admin/src/components/FormModal/Input/index.js +2 -2
  3. package/admin/src/components/FormModal/index.js +10 -5
  4. package/admin/src/components/Permissions/PermissionRow/CheckboxWrapper.js +1 -1
  5. package/admin/src/components/Permissions/PermissionRow/SubCategory.js +12 -10
  6. package/admin/src/components/Permissions/PermissionRow/index.js +1 -1
  7. package/admin/src/components/Permissions/index.js +12 -8
  8. package/admin/src/components/Policies/index.js +12 -9
  9. package/admin/src/components/UsersPermissions/index.js +12 -15
  10. package/admin/src/index.js +0 -8
  11. package/admin/src/pages/AdvancedSettings/index.js +13 -13
  12. package/admin/src/pages/EmailTemplates/components/EmailForm.js +10 -5
  13. package/admin/src/pages/EmailTemplates/components/EmailTable.js +16 -16
  14. package/admin/src/pages/EmailTemplates/index.js +3 -3
  15. package/admin/src/pages/Providers/index.js +21 -21
  16. package/admin/src/pages/Providers/utils/api.js +1 -1
  17. package/admin/src/pages/Roles/CreatePage/index.js +13 -13
  18. package/admin/src/pages/Roles/EditPage/index.js +23 -13
  19. package/admin/src/pages/Roles/ListPage/components/TableBody.js +14 -10
  20. package/admin/src/pages/Roles/ListPage/index.js +19 -25
  21. package/documentation/1.0.0/overrides/users-permissions-User.json +7 -7
  22. package/package.json +29 -30
  23. package/server/bootstrap/index.js +17 -17
  24. package/server/config.js +2 -2
  25. package/server/content-types/permission/index.js +3 -0
  26. package/server/content-types/role/index.js +3 -0
  27. package/server/controllers/auth.js +73 -215
  28. package/server/controllers/{user/admin.js → content-manager-user.js} +44 -75
  29. package/server/controllers/index.js +2 -0
  30. package/server/controllers/role.js +7 -7
  31. package/server/controllers/settings.js +5 -4
  32. package/server/controllers/user.js +118 -28
  33. package/server/controllers/validation/auth.js +29 -0
  34. package/server/controllers/validation/user.js +38 -0
  35. package/server/middlewares/rateLimit.js +1 -1
  36. package/server/routes/admin/role.js +5 -5
  37. package/server/routes/admin/settings.js +6 -6
  38. package/server/routes/content-api/auth.js +5 -7
  39. package/server/services/jwt.js +9 -17
  40. package/server/services/providers.js +13 -10
  41. package/server/services/role.js +5 -10
  42. package/server/services/user.js +8 -6
  43. package/server/services/users-permissions.js +56 -45
  44. package/server/strategies/users-permissions.js +23 -22
  45. package/admin/src/assets/images/logo.svg +0 -1
  46. package/server/controllers/user/api.js +0 -158
@@ -9,7 +9,7 @@ module.exports = [
9
9
  policies: [
10
10
  {
11
11
  name: 'admin::hasPermissions',
12
- options: {
12
+ config: {
13
13
  actions: ['plugin::users-permissions.email-templates.read'],
14
14
  },
15
15
  },
@@ -24,7 +24,7 @@ module.exports = [
24
24
  policies: [
25
25
  {
26
26
  name: 'admin::hasPermissions',
27
- options: {
27
+ config: {
28
28
  actions: ['plugin::users-permissions.email-templates.update'],
29
29
  },
30
30
  },
@@ -39,7 +39,7 @@ module.exports = [
39
39
  policies: [
40
40
  {
41
41
  name: 'admin::hasPermissions',
42
- options: {
42
+ config: {
43
43
  actions: ['plugin::users-permissions.advanced-settings.read'],
44
44
  },
45
45
  },
@@ -54,7 +54,7 @@ module.exports = [
54
54
  policies: [
55
55
  {
56
56
  name: 'admin::hasPermissions',
57
- options: {
57
+ config: {
58
58
  actions: ['plugin::users-permissions.advanced-settings.update'],
59
59
  },
60
60
  },
@@ -69,7 +69,7 @@ module.exports = [
69
69
  policies: [
70
70
  {
71
71
  name: 'admin::hasPermissions',
72
- options: {
72
+ config: {
73
73
  actions: ['plugin::users-permissions.providers.read'],
74
74
  },
75
75
  },
@@ -85,7 +85,7 @@ module.exports = [
85
85
  policies: [
86
86
  {
87
87
  name: 'admin::hasPermissions',
88
- options: {
88
+ config: {
89
89
  actions: ['plugin::users-permissions.providers.update'],
90
90
  },
91
91
  },
@@ -1,14 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const { rateLimit } = require('../../middlewares');
4
-
5
3
  module.exports = [
6
4
  {
7
5
  method: 'GET',
8
6
  path: '/connect/(.*)',
9
7
  handler: 'auth.connect',
10
8
  config: {
11
- middlewares: [rateLimit],
9
+ middlewares: ['plugin::users-permissions.rateLimit'],
12
10
  prefix: '',
13
11
  },
14
12
  },
@@ -17,7 +15,7 @@ module.exports = [
17
15
  path: '/auth/local',
18
16
  handler: 'auth.callback',
19
17
  config: {
20
- middlewares: [rateLimit],
18
+ middlewares: ['plugin::users-permissions.rateLimit'],
21
19
  prefix: '',
22
20
  },
23
21
  },
@@ -26,7 +24,7 @@ module.exports = [
26
24
  path: '/auth/local/register',
27
25
  handler: 'auth.register',
28
26
  config: {
29
- middlewares: [rateLimit],
27
+ middlewares: ['plugin::users-permissions.rateLimit'],
30
28
  prefix: '',
31
29
  },
32
30
  },
@@ -43,7 +41,7 @@ module.exports = [
43
41
  path: '/auth/forgot-password',
44
42
  handler: 'auth.forgotPassword',
45
43
  config: {
46
- middlewares: [rateLimit],
44
+ middlewares: ['plugin::users-permissions.rateLimit'],
47
45
  prefix: '',
48
46
  },
49
47
  },
@@ -52,7 +50,7 @@ module.exports = [
52
50
  path: '/auth/reset-password',
53
51
  handler: 'auth.resetPassword',
54
52
  config: {
55
- middlewares: [rateLimit],
53
+ middlewares: ['plugin::users-permissions.rateLimit'],
56
54
  prefix: '',
57
55
  },
58
56
  },
@@ -11,28 +11,20 @@ const jwt = require('jsonwebtoken');
11
11
 
12
12
  module.exports = ({ strapi }) => ({
13
13
  getToken(ctx) {
14
- const params = _.assign({}, ctx.request.body, ctx.request.query);
15
-
16
- let token = '';
14
+ let token;
17
15
 
18
16
  if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
19
- const parts = ctx.request.header.authorization.split(' ');
17
+ const parts = ctx.request.header.authorization.split(/\s+/);
20
18
 
21
- if (parts.length === 2) {
22
- const scheme = parts[0];
23
- const credentials = parts[1];
24
- if (/^Bearer$/i.test(scheme)) {
25
- token = credentials;
26
- }
27
- } else {
28
- throw new Error(
29
- 'Invalid authorization header format. Format is Authorization: Bearer [token]'
30
- );
19
+ if (parts[0].toLowerCase() !== 'bearer' || parts.length !== 2) {
20
+ return null;
31
21
  }
32
- } else if (params.token) {
33
- token = params.token;
22
+
23
+ token = parts[1];
24
+ } else if (ctx.query.access_token) {
25
+ token = ctx.query.access_token;
34
26
  } else {
35
- throw new Error('No authorization header was found');
27
+ return null;
36
28
  }
37
29
 
38
30
  return this.verify(token);
@@ -7,6 +7,7 @@
7
7
  // Public node modules.
8
8
  const _ = require('lodash');
9
9
  const jwt = require('jsonwebtoken');
10
+ const urlJoin = require('url-join');
10
11
 
11
12
  const { getAbsoluteServerUrl } = require('@strapi/utils');
12
13
 
@@ -27,7 +28,7 @@ module.exports = ({ strapi }) => {
27
28
  const getProfile = async (provider, query, callback) => {
28
29
  const access_token = query.access_token || query.code || query.oauth_token;
29
30
 
30
- const grant = await strapi
31
+ const providers = await strapi
31
32
  .store({ type: 'plugin', name: 'users-permissions', key: 'grant' })
32
33
  .get();
33
34
 
@@ -200,8 +201,8 @@ module.exports = ({ strapi }) => {
200
201
  const twitter = purest({
201
202
  provider: 'twitter',
202
203
  config: purestConfig,
203
- key: grant.twitter.key,
204
- secret: grant.twitter.secret,
204
+ key: providers.twitter.key,
205
+ secret: providers.twitter.secret,
205
206
  });
206
207
 
207
208
  twitter
@@ -224,8 +225,8 @@ module.exports = ({ strapi }) => {
224
225
  case 'instagram': {
225
226
  const instagram = purest({
226
227
  provider: 'instagram',
227
- key: grant.instagram.key,
228
- secret: grant.instagram.secret,
228
+ key: providers.instagram.key,
229
+ secret: providers.instagram.secret,
229
230
  config: purestConfig,
230
231
  });
231
232
 
@@ -297,7 +298,7 @@ module.exports = ({ strapi }) => {
297
298
 
298
299
  twitch
299
300
  .get('users')
300
- .auth(access_token, grant.twitch.key)
301
+ .auth(access_token, providers.twitch.key)
301
302
  .request((err, res, body) => {
302
303
  if (err) {
303
304
  callback(err);
@@ -402,7 +403,7 @@ module.exports = ({ strapi }) => {
402
403
  }
403
404
  case 'auth0': {
404
405
  const purestAuth0Conf = {};
405
- purestAuth0Conf[`https://${grant.auth0.subdomain}.auth0.com`] = {
406
+ purestAuth0Conf[`https://${providers.auth0.subdomain}.auth0.com`] = {
406
407
  __domain: {
407
408
  auth: {
408
409
  auth: { bearer: '[0]' },
@@ -441,7 +442,7 @@ module.exports = ({ strapi }) => {
441
442
  break;
442
443
  }
443
444
  case 'cas': {
444
- const provider_url = 'https://' + _.get(grant['cas'], 'subdomain');
445
+ const provider_url = 'https://' + _.get(providers.cas, 'subdomain');
445
446
  const cas = purest({
446
447
  provider: 'cas',
447
448
  config: {
@@ -586,8 +587,10 @@ module.exports = ({ strapi }) => {
586
587
  });
587
588
  };
588
589
 
589
- const buildRedirectUri = (provider = '') =>
590
- `${getAbsoluteServerUrl(strapi.config)}/api/connect/${provider}/callback`;
590
+ const buildRedirectUri = (provider = '') => {
591
+ const apiPrefix = strapi.config.get('api.rest.prefix');
592
+ return urlJoin(getAbsoluteServerUrl(strapi.config), apiPrefix, 'connect', provider, 'callback');
593
+ };
591
594
 
592
595
  return {
593
596
  connect,
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const _ = require('lodash');
4
+ const { NotFoundError } = require('@strapi/utils').errors;
4
5
  const { getService } = require('../utils');
5
6
 
6
7
  module.exports = ({ strapi }) => ({
@@ -40,13 +41,13 @@ module.exports = ({ strapi }) => ({
40
41
  await Promise.all(createPromises);
41
42
  },
42
43
 
43
- async getRole(roleID, plugins) {
44
+ async getRole(roleID) {
44
45
  const role = await strapi
45
46
  .query('plugin::users-permissions.role')
46
47
  .findOne({ where: { id: roleID }, populate: ['permissions'] });
47
48
 
48
49
  if (!role) {
49
- throw new Error('Role not found');
50
+ throw new NotFoundError('Role not found');
50
51
  }
51
52
 
52
53
  const allActions = getService('users-permissions').getActions();
@@ -59,12 +60,6 @@ module.exports = ({ strapi }) => ({
59
60
  enabled: true,
60
61
  policy: '',
61
62
  });
62
-
63
- if (permission.action.startsWith('plugin')) {
64
- const [, pluginName] = type.split('::');
65
-
66
- allActions[type].information = plugins.find(plugin => plugin.id === pluginName) || {};
67
- }
68
63
  });
69
64
 
70
65
  return {
@@ -91,7 +86,7 @@ module.exports = ({ strapi }) => ({
91
86
  .findOne({ where: { id: roleID }, populate: ['permissions'] });
92
87
 
93
88
  if (!role) {
94
- throw new Error('Role not found');
89
+ throw new NotFoundError('Role not found');
95
90
  }
96
91
 
97
92
  await strapi.query('plugin::users-permissions.role').update({
@@ -153,7 +148,7 @@ module.exports = ({ strapi }) => ({
153
148
  .findOne({ where: { id: roleID }, populate: ['users', 'permissions'] });
154
149
 
155
150
  if (!role) {
156
- throw new Error('Role not found');
151
+ throw new NotFoundError('Role not found');
157
152
  }
158
153
 
159
154
  // Move users to guest role.
@@ -9,7 +9,7 @@
9
9
  const crypto = require('crypto');
10
10
  const bcrypt = require('bcryptjs');
11
11
 
12
- const { sanitizeEntity, getAbsoluteServerUrl } = require('@strapi/utils');
12
+ const { getAbsoluteServerUrl, sanitize } = require('@strapi/utils');
13
13
  const { getService } = require('../utils');
14
14
 
15
15
  module.exports = ({ strapi }) => ({
@@ -121,14 +121,14 @@ module.exports = ({ strapi }) => ({
121
121
  async sendConfirmationEmail(user) {
122
122
  const userPermissionService = getService('users-permissions');
123
123
  const pluginStore = await strapi.store({ type: 'plugin', name: 'users-permissions' });
124
+ const userSchema = strapi.getModel('plugin::users-permissions.user');
124
125
 
125
126
  const settings = await pluginStore
126
127
  .get({ key: 'email' })
127
128
  .then(storeEmail => storeEmail['email_confirmation'].options);
128
129
 
129
- const userInfo = sanitizeEntity(user, {
130
- model: strapi.getModel('plugin::users-permissions.user'),
131
- });
130
+ // Sanitize the template's user information
131
+ const sanitizedUserInfo = await sanitize.sanitizers.defaultSanitizeOutput(userSchema, user);
132
132
 
133
133
  const confirmationToken = crypto.randomBytes(20).toString('hex');
134
134
 
@@ -136,11 +136,13 @@ module.exports = ({ strapi }) => ({
136
136
 
137
137
  settings.message = await userPermissionService.template(settings.message, {
138
138
  URL: `${getAbsoluteServerUrl(strapi.config)}/auth/email-confirmation`,
139
- USER: userInfo,
139
+ USER: sanitizedUserInfo,
140
140
  CODE: confirmationToken,
141
141
  });
142
142
 
143
- settings.object = await userPermissionService.template(settings.object, { USER: userInfo });
143
+ settings.object = await userPermissionService.template(settings.object, {
144
+ USER: sanitizedUserInfo,
145
+ });
144
146
 
145
147
  // Send an email to the user.
146
148
  await strapi
@@ -28,63 +28,74 @@ const transformRoutePrefixFor = pluginName => route => {
28
28
  };
29
29
 
30
30
  module.exports = ({ strapi }) => ({
31
- getPlugins(lang = 'en') {
32
- const request = require('request');
33
- return new Promise(resolve => {
34
- request(
35
- {
36
- uri: `https://marketplace.strapi.io/plugins?lang=${lang}`,
37
- json: true,
38
- timeout: 3000,
39
- headers: {
40
- 'cache-control': 'max-age=3600',
41
- },
42
- },
43
- (err, response, body) => {
44
- if (err || response.statusCode !== 200) {
45
- return resolve([]);
46
- }
47
-
48
- resolve(body);
49
- }
50
- );
51
- });
52
- },
53
-
54
- // TODO: Filter on content-api only
55
31
  getActions({ defaultEnable = false } = {}) {
56
32
  const actionMap = {};
57
33
 
34
+ const isContentApi = action => {
35
+ if (!_.has(action, Symbol.for('__type__'))) {
36
+ return false;
37
+ }
38
+
39
+ return action[Symbol.for('__type__')].includes('content-api');
40
+ };
41
+
58
42
  _.forEach(strapi.api, (api, apiName) => {
59
- const controllers = _.mapValues(api.controllers, controller => {
60
- return _.mapValues(controller, () => {
61
- return {
62
- enabled: defaultEnable,
63
- policy: '',
64
- };
65
- });
66
- });
43
+ const controllers = _.reduce(
44
+ api.controllers,
45
+ (acc, controller, controllerName) => {
46
+ const contentApiActions = _.pickBy(controller, isContentApi);
47
+
48
+ if (_.isEmpty(contentApiActions)) {
49
+ return acc;
50
+ }
51
+
52
+ acc[controllerName] = _.mapValues(contentApiActions, () => {
53
+ return {
54
+ enabled: defaultEnable,
55
+ policy: '',
56
+ };
57
+ });
67
58
 
68
- actionMap[`api::${apiName}`] = { controllers };
59
+ return acc;
60
+ },
61
+ {}
62
+ );
63
+
64
+ if (!_.isEmpty(controllers)) {
65
+ actionMap[`api::${apiName}`] = { controllers };
66
+ }
69
67
  });
70
68
 
71
69
  _.forEach(strapi.plugins, (plugin, pluginName) => {
72
- const controllers = _.mapValues(plugin.controllers, controller => {
73
- return _.mapValues(controller, () => {
74
- return {
75
- enabled: defaultEnable,
76
- policy: '',
77
- };
78
- });
79
- });
70
+ const controllers = _.reduce(
71
+ plugin.controllers,
72
+ (acc, controller, controllerName) => {
73
+ const contentApiActions = _.pickBy(controller, isContentApi);
74
+
75
+ if (_.isEmpty(contentApiActions)) {
76
+ return acc;
77
+ }
80
78
 
81
- actionMap[`plugin::${pluginName}`] = { controllers };
79
+ acc[controllerName] = _.mapValues(contentApiActions, () => {
80
+ return {
81
+ enabled: defaultEnable,
82
+ policy: '',
83
+ };
84
+ });
85
+
86
+ return acc;
87
+ },
88
+ {}
89
+ );
90
+
91
+ if (!_.isEmpty(controllers)) {
92
+ actionMap[`plugin::${pluginName}`] = { controllers };
93
+ }
82
94
  });
83
95
 
84
96
  return actionMap;
85
97
  },
86
98
 
87
- // TODO: Filter on content-api only
88
99
  async getRoutes() {
89
100
  const routesMap = {};
90
101
 
@@ -95,7 +106,7 @@ module.exports = ({ strapi }) => ({
95
106
  }
96
107
 
97
108
  return route;
98
- });
109
+ }).filter(route => route.info.type === 'content-api');
99
110
 
100
111
  if (routes.length === 0) {
101
112
  return;
@@ -116,7 +127,7 @@ module.exports = ({ strapi }) => ({
116
127
  }
117
128
 
118
129
  return transformPrefix(route);
119
- });
130
+ }).filter(route => route.info.type === 'content-api');
120
131
 
121
132
  if (routes.length === 0) {
122
133
  return;
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { castArray, map } = require('lodash/fp');
4
+ const { ForbiddenError, UnauthorizedError } = require('@strapi/utils').errors;
4
5
 
5
6
  const { getService } = require('../utils');
6
7
 
@@ -9,9 +10,11 @@ const getAdvancedSettings = () => {
9
10
  };
10
11
 
11
12
  const authenticate = async ctx => {
12
- if (ctx.request && ctx.request.header && ctx.request.header.authorization) {
13
- try {
14
- const { id } = await getService('jwt').getToken(ctx);
13
+ try {
14
+ const token = await getService('jwt').getToken(ctx);
15
+
16
+ if (token) {
17
+ const { id } = token;
15
18
 
16
19
  if (id === undefined) {
17
20
  return { authenticated: false };
@@ -40,30 +43,28 @@ const authenticate = async ctx => {
40
43
  authenticated: true,
41
44
  credentials: user,
42
45
  };
43
- } catch (err) {
44
- return { authenticated: false };
45
46
  }
46
- }
47
47
 
48
- const publicPermissions = await strapi.query('plugin::users-permissions.permission').findMany({
49
- where: {
50
- role: { type: 'public' },
51
- },
52
- });
48
+ const publicPermissions = await strapi.query('plugin::users-permissions.permission').findMany({
49
+ where: {
50
+ role: { type: 'public' },
51
+ },
52
+ });
53
53
 
54
- if (publicPermissions.length === 0) {
54
+ if (publicPermissions.length === 0) {
55
+ return { authenticated: false };
56
+ }
57
+
58
+ return {
59
+ authenticated: true,
60
+ credentials: null,
61
+ };
62
+ } catch (err) {
55
63
  return { authenticated: false };
56
64
  }
57
-
58
- return {
59
- authenticated: true,
60
- credentials: null,
61
- };
62
65
  };
63
66
 
64
67
  const verify = async (auth, config) => {
65
- const { errors } = strapi.container.get('auth');
66
-
67
68
  const { credentials: user } = auth;
68
69
 
69
70
  // public accesss
@@ -79,13 +80,13 @@ const verify = async (auth, config) => {
79
80
 
80
81
  // A non authenticated user cannot access routes that do not have a scope
81
82
  if (!config.scope) {
82
- throw new errors.UnauthorizedError();
83
+ throw new UnauthorizedError();
83
84
  }
84
85
 
85
86
  const isAllowed = castArray(config.scope).every(scope => allowedActions.includes(scope));
86
87
 
87
88
  if (!isAllowed) {
88
- throw new errors.ForbiddenError();
89
+ throw new ForbiddenError();
89
90
  }
90
91
 
91
92
  return;
@@ -105,7 +106,7 @@ const verify = async (auth, config) => {
105
106
  const isAllowed = castArray(config.scope).every(scope => allowedActions.includes(scope));
106
107
 
107
108
  if (!isAllowed) {
108
- throw new errors.ForbiddenError();
109
+ throw new ForbiddenError();
109
110
  }
110
111
 
111
112
  // TODO: if we need to keep policies for u&p execution
@@ -1 +0,0 @@
1
- <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><text transform="translate(-24 -6)" fill="#4B515A" fill-rule="evenodd" font-size="24" font-family="AppleColorEmoji, Apple Color Emoji"><tspan x="24" y="28">🔐</tspan></text></svg>