@strapi/plugin-users-permissions 4.5.0-alpha.0 → 4.5.0

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.
@@ -695,7 +695,7 @@ paths:
695
695
  type: string
696
696
  description: user Id
697
697
  responses:
698
- "200":
698
+ '200':
699
699
  description: Returns deleted user info
700
700
  content:
701
701
  application/json:
@@ -868,4 +868,3 @@ components:
868
868
  controllerA:
869
869
  find:
870
870
  enabled: true
871
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/plugin-users-permissions",
3
- "version": "4.5.0-alpha.0",
3
+ "version": "4.5.0",
4
4
  "description": "Protect your API with a full-authentication process based on JWT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,13 +27,13 @@
27
27
  "test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
28
28
  },
29
29
  "dependencies": {
30
- "@strapi/helper-plugin": "4.5.0-alpha.0",
31
- "@strapi/utils": "4.5.0-alpha.0",
30
+ "@strapi/helper-plugin": "4.5.0",
31
+ "@strapi/utils": "4.5.0",
32
32
  "bcryptjs": "2.4.3",
33
33
  "grant-koa": "5.4.8",
34
34
  "jsonwebtoken": "^8.1.0",
35
35
  "koa": "^2.13.4",
36
- "koa2-ratelimit": "^1.1.1",
36
+ "koa2-ratelimit": "^1.1.2",
37
37
  "lodash": "4.17.21",
38
38
  "purest": "4.0.2",
39
39
  "react": "^17.0.2",
@@ -64,5 +64,5 @@
64
64
  "required": true,
65
65
  "kind": "plugin"
66
66
  },
67
- "gitHead": "c9a98c4dbcf3c4f2a449f8d96e7cbe4cd9b1e0f5"
67
+ "gitHead": "33debd57010667a3fc5dfa343a673206cfb956e1"
68
68
  }
@@ -10,13 +10,37 @@ const createUserBodySchema = yup.object().shape({
10
10
  email: yup.string().email().required(),
11
11
  username: yup.string().min(1).required(),
12
12
  password: yup.string().min(1).required(),
13
- role: yup.strapiID(),
13
+ role: yup.lazy((value) =>
14
+ typeof value === 'object'
15
+ ? yup
16
+ .object()
17
+ .shape({
18
+ connect: yup
19
+ .array()
20
+ .of(yup.object().shape({ id: yup.strapiID().required() }))
21
+ .min(1)
22
+ .required(),
23
+ })
24
+ .required()
25
+ : yup.strapiID().required()
26
+ ),
14
27
  });
15
28
 
16
29
  const updateUserBodySchema = yup.object().shape({
17
30
  email: yup.string().email().min(1),
18
31
  username: yup.string().min(1),
19
32
  password: yup.string().min(1),
33
+ role: yup.lazy((value) =>
34
+ typeof value === 'object'
35
+ ? yup.object().shape({
36
+ connect: yup
37
+ .array()
38
+ .of(yup.object().shape({ id: yup.strapiID().required() }))
39
+ .min(1)
40
+ .required(),
41
+ })
42
+ : yup.strapiID()
43
+ ),
20
44
  });
21
45
 
22
46
  module.exports = {
@@ -6,6 +6,7 @@ const user = require('./user');
6
6
  const role = require('./role');
7
7
  const usersPermissions = require('./users-permissions');
8
8
  const providersRegistry = require('./providers-registry');
9
+ const permission = require('./permission');
9
10
 
10
11
  module.exports = {
11
12
  jwt,
@@ -14,4 +15,5 @@ module.exports = {
14
15
  role,
15
16
  user,
16
17
  'users-permissions': usersPermissions,
18
+ permission,
17
19
  };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const PUBLIC_ROLE_FILTER = { role: { type: 'public' } };
4
+
5
+ module.exports = ({ strapi }) => ({
6
+ /**
7
+ * Find permissions associated to a specific role ID
8
+ *
9
+ * @param {number} roleID
10
+ *
11
+ * @return {object[]}
12
+ */
13
+ async findRolePermissions(roleID) {
14
+ return strapi.entityService.load(
15
+ 'plugin::users-permissions.role',
16
+ { id: roleID },
17
+ 'permissions'
18
+ );
19
+ },
20
+
21
+ /**
22
+ * Find permissions for the public role
23
+ *
24
+ * @return {object[]}
25
+ */
26
+ async findPublicPermissions() {
27
+ return strapi.entityService.findMany('plugin::users-permissions.permission', {
28
+ filters: PUBLIC_ROLE_FILTER,
29
+ });
30
+ },
31
+
32
+ /**
33
+ * Transform a Users-Permissions' action into a content API one
34
+ *
35
+ * @param {object} permission
36
+ * @param {string} permission.action
37
+ *
38
+ * @return {{ action: string }}
39
+ */
40
+ toContentAPIPermission(permission) {
41
+ const { action } = permission;
42
+
43
+ return { action };
44
+ },
45
+ });
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { castArray, map } = require('lodash/fp');
3
+ const { castArray, map, every, pipe } = require('lodash/fp');
4
4
  const { ForbiddenError, UnauthorizedError } = require('@strapi/utils').errors;
5
5
 
6
6
  const { getService } = require('../utils');
@@ -16,48 +16,61 @@ const authenticate = async (ctx) => {
16
16
  if (token) {
17
17
  const { id } = token;
18
18
 
19
+ // Invalid token
19
20
  if (id === undefined) {
20
21
  return { authenticated: false };
21
22
  }
22
23
 
23
- // fetch authenticated user
24
24
  const user = await getService('user').fetchAuthenticatedUser(id);
25
25
 
26
+ // No user associated to the token
26
27
  if (!user) {
27
28
  return { error: 'Invalid credentials' };
28
29
  }
29
30
 
30
31
  const advancedSettings = await getAdvancedSettings();
31
32
 
33
+ // User not confirmed
32
34
  if (advancedSettings.email_confirmation && !user.confirmed) {
33
35
  return { error: 'Invalid credentials' };
34
36
  }
35
37
 
38
+ // User blocked
36
39
  if (user.blocked) {
37
40
  return { error: 'Invalid credentials' };
38
41
  }
39
42
 
43
+ // Fetch user's permissions
44
+ const permissions = await Promise.resolve(user.role.id)
45
+ .then(getService('permission').findRolePermissions)
46
+ .then(map(getService('permission').toContentAPIPermission));
47
+
48
+ // Generate an ability (content API engine) based on the given permissions
49
+ const ability = await strapi.contentAPI.permissions.engine.generateAbility(permissions);
50
+
40
51
  ctx.state.user = user;
41
52
 
42
53
  return {
43
54
  authenticated: true,
44
55
  credentials: user,
56
+ ability,
45
57
  };
46
58
  }
47
59
 
48
- const publicPermissions = await strapi.query('plugin::users-permissions.permission').findMany({
49
- where: {
50
- role: { type: 'public' },
51
- },
52
- });
60
+ const publicPermissions = await getService('permission')
61
+ .findPublicPermissions()
62
+ .then(map(getService('permission').toContentAPIPermission));
53
63
 
54
64
  if (publicPermissions.length === 0) {
55
65
  return { authenticated: false };
56
66
  }
57
67
 
68
+ const ability = await strapi.contentAPI.permissions.engine.generateAbility(publicPermissions);
69
+
58
70
  return {
59
71
  authenticated: true,
60
72
  credentials: null,
73
+ ability,
61
74
  };
62
75
  } catch (err) {
63
76
  return { authenticated: false };
@@ -65,7 +78,7 @@ const authenticate = async (ctx) => {
65
78
  };
66
79
 
67
80
  const verify = async (auth, config) => {
68
- const { credentials: user } = auth;
81
+ const { credentials: user, ability } = auth;
69
82
 
70
83
  if (!config.scope) {
71
84
  if (!user) {
@@ -77,18 +90,17 @@ const verify = async (auth, config) => {
77
90
  }
78
91
  }
79
92
 
80
- let { allowedActions } = auth;
81
-
82
- if (!allowedActions) {
83
- const permissions = await strapi.query('plugin::users-permissions.permission').findMany({
84
- where: { role: user ? user.role.id : { type: 'public' } },
85
- });
86
-
87
- allowedActions = map('action', permissions);
88
- auth.allowedActions = allowedActions;
93
+ // If no ability have been generated, then consider auth is missing
94
+ if (!ability) {
95
+ throw new UnauthorizedError();
89
96
  }
90
97
 
91
- const isAllowed = castArray(config.scope).every((scope) => allowedActions.includes(scope));
98
+ const isAllowed = pipe(
99
+ // Make sure we're dealing with an array
100
+ castArray,
101
+ // Transform the scope array into an action array
102
+ every((scope) => ability.can(scope))
103
+ )(config.scope);
92
104
 
93
105
  if (!isAllowed) {
94
106
  throw new ForbiddenError();
@@ -3,6 +3,7 @@ import * as user from '../services/user';
3
3
  import * as role from '../services/role';
4
4
  import * as jwt from '../services/jwt';
5
5
  import * as providers from '../services/providers';
6
+ import * as permission from '../services/permission';
6
7
 
7
8
  type S = {
8
9
  ['users-permissions']: typeof usersPermissions;
@@ -11,6 +12,7 @@ type S = {
11
12
  jwt: typeof jwt;
12
13
  providers: typeof providers;
13
14
  ['providers-registry']: typeof providers;
15
+ permission: typeof permission;
14
16
  };
15
17
 
16
18
  export function getService<T extends keyof S>(name: T): ReturnType<S[T]>;