@strapi/admin 4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d → 4.7.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.
Files changed (129) hide show
  1. package/admin/src/components/Notifications/Notification/index.js +8 -1
  2. package/admin/src/hooks/index.js +2 -0
  3. package/admin/src/hooks/useLicenseLimitNotification/index.js +5 -0
  4. package/admin/src/hooks/useLicenseLimits/index.js +3 -0
  5. package/admin/src/pages/HomePage/index.js +2 -0
  6. package/admin/src/pages/SettingsPage/components/Tokens/LifeSpanInput/index.js +1 -1
  7. package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +5 -5
  8. package/admin/src/pages/SettingsPage/components/Tokens/Table/index.js +1 -1
  9. package/admin/src/pages/SettingsPage/components/Tokens/TokenBox/index.js +1 -1
  10. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +4 -4
  11. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +5 -5
  12. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +2 -2
  13. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +5 -0
  14. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/index.js +2 -0
  15. package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/utils/tableHeaders.js +4 -4
  16. package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +1 -4
  17. package/admin/src/pages/SettingsPage/pages/Users/ListPage/CreateAction/index.js +24 -0
  18. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +4 -1
  19. package/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js +25 -31
  20. package/admin/src/translations/ca.json +6 -6
  21. package/admin/src/translations/de.json +6 -6
  22. package/admin/src/translations/dk.json +6 -6
  23. package/admin/src/translations/en.json +209 -184
  24. package/admin/src/translations/es.json +6 -6
  25. package/admin/src/translations/eu.json +19 -19
  26. package/admin/src/translations/fr.json +6 -6
  27. package/admin/src/translations/hi.json +6 -6
  28. package/admin/src/translations/hu.json +19 -19
  29. package/admin/src/translations/ja.json +6 -6
  30. package/admin/src/translations/ko.json +6 -6
  31. package/admin/src/translations/ml.json +6 -6
  32. package/admin/src/translations/nl.json +19 -19
  33. package/admin/src/translations/pl.json +6 -6
  34. package/admin/src/translations/pt-BR.json +6 -6
  35. package/admin/src/translations/ru.json +865 -785
  36. package/admin/src/translations/sa.json +6 -6
  37. package/admin/src/translations/sk.json +2 -2
  38. package/admin/src/translations/sv.json +19 -19
  39. package/admin/src/translations/tr.json +19 -19
  40. package/admin/src/translations/zh-Hans.json +6 -6
  41. package/admin/src/translations/zh.json +19 -19
  42. package/build/4649.ffa2f59a.chunk.js +30 -0
  43. package/build/7259.cd2f7bad.chunk.js +1 -0
  44. package/build/{Admin-authenticatedApp.49265c1c.chunk.js → Admin-authenticatedApp.bce108cd.chunk.js} +6 -6
  45. package/build/Admin_homePage.cec3f510.chunk.js +70 -0
  46. package/build/Admin_settingsPage.f6d02df6.chunk.js +178 -0
  47. package/build/admin-app.d3b3237b.chunk.js +112 -0
  48. package/build/admin-edit-users.f06c4a53.chunk.js +10 -0
  49. package/build/admin-users.8c9bfda4.chunk.js +11 -0
  50. package/build/audit-logs-settings-page.7be97e82.chunk.js +1 -0
  51. package/build/ca-json.59c4502c.chunk.js +1 -0
  52. package/build/de-json.dbc2cf1b.chunk.js +1 -0
  53. package/build/dk-json.52f67b15.chunk.js +1 -0
  54. package/build/en-json.e688dfe2.chunk.js +1 -0
  55. package/build/es-json.c40c57dd.chunk.js +1 -0
  56. package/build/eu-json.6702a0d2.chunk.js +1 -0
  57. package/build/fr-json.ea9ec573.chunk.js +1 -0
  58. package/build/hi-json.14a17920.chunk.js +1 -0
  59. package/build/hu-json.33172d09.chunk.js +1 -0
  60. package/build/index.html +1 -1
  61. package/build/ja-json.3008b720.chunk.js +1 -0
  62. package/build/ko-json.7d2f95b1.chunk.js +1 -0
  63. package/build/ml-json.3e69969b.chunk.js +1 -0
  64. package/build/nl-json.641782d5.chunk.js +1 -0
  65. package/build/pl-json.05814145.chunk.js +1 -0
  66. package/build/pt-BR-json.d72350de.chunk.js +1 -0
  67. package/build/ru-json.c4a4f50b.chunk.js +1 -0
  68. package/build/{runtime~main.86cd21d5.js → runtime~main.bc7de2d8.js} +2 -2
  69. package/build/sa-json.e5e7ccaf.chunk.js +1 -0
  70. package/build/{sk-json.7bbeb0af.chunk.js → sk-json.3529b8aa.chunk.js} +1 -1
  71. package/build/sv-json.207afc0d.chunk.js +1 -0
  72. package/build/tr-json.f1a0d19d.chunk.js +1 -0
  73. package/build/{transfer-tokens-list-page.1e15926d.chunk.js → transfer-tokens-list-page.c6f8039a.chunk.js} +1 -1
  74. package/build/zh-Hans-json.6e26e359.chunk.js +1 -0
  75. package/build/zh-json.085a34f4.chunk.js +1 -0
  76. package/ee/admin/hooks/index.js +2 -0
  77. package/ee/admin/hooks/useLicenseLimitNotification/index.js +87 -0
  78. package/ee/admin/hooks/useLicenseLimits/index.js +31 -0
  79. package/ee/admin/pages/SettingsPage/pages/ApplicationInfosPage/components/AdminSeatInfo/index.js +88 -0
  80. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/Modal/ActionBody.js +1 -1
  81. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +21 -10
  82. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/tableHeaders.js +1 -1
  83. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/CreateAction/index.js +51 -0
  84. package/ee/server/bootstrap.js +1 -1
  85. package/ee/server/controllers/admin.js +48 -0
  86. package/ee/server/controllers/index.js +1 -0
  87. package/ee/server/controllers/user.js +95 -3
  88. package/ee/server/routes/index.js +23 -0
  89. package/ee/server/services/audit-logs.js +15 -5
  90. package/ee/server/services/index.js +2 -0
  91. package/ee/server/services/seat-enforcement.js +114 -0
  92. package/ee/server/services/user.js +234 -0
  93. package/package.json +9 -9
  94. package/server/middlewares/data-transfer.js +26 -0
  95. package/server/middlewares/index.js +1 -0
  96. package/server/routes/transfer.js +7 -9
  97. package/server/services/transfer/index.js +1 -0
  98. package/server/services/transfer/token.js +18 -3
  99. package/server/services/transfer/utils.js +38 -0
  100. package/admin/src/pages/SettingsPage/components/Tokens/FormiTokenContainer/LifeSpanInput.js +0 -95
  101. package/build/4649.b7e84a29.chunk.js +0 -30
  102. package/build/7259.3f04094f.chunk.js +0 -1
  103. package/build/Admin_homePage.1f10437f.chunk.js +0 -78
  104. package/build/Admin_settingsPage.5a329b58.chunk.js +0 -178
  105. package/build/admin-app.df9adf93.chunk.js +0 -112
  106. package/build/admin-edit-users.08a60ea2.chunk.js +0 -10
  107. package/build/admin-users.74f5629d.chunk.js +0 -11
  108. package/build/audit-logs-settings-page.bc1784fe.chunk.js +0 -1
  109. package/build/ca-json.4d999055.chunk.js +0 -1
  110. package/build/de-json.866f8a28.chunk.js +0 -1
  111. package/build/dk-json.10f7b1d1.chunk.js +0 -1
  112. package/build/en-json.8e5451b1.chunk.js +0 -1
  113. package/build/es-json.ea15c957.chunk.js +0 -1
  114. package/build/eu-json.3bc24d60.chunk.js +0 -1
  115. package/build/fr-json.e88fbdfd.chunk.js +0 -1
  116. package/build/hi-json.df3a7be2.chunk.js +0 -1
  117. package/build/hu-json.680e6eef.chunk.js +0 -1
  118. package/build/ja-json.97ee41ba.chunk.js +0 -1
  119. package/build/ko-json.4cbbf4f2.chunk.js +0 -1
  120. package/build/ml-json.e3747091.chunk.js +0 -1
  121. package/build/nl-json.371a15ee.chunk.js +0 -1
  122. package/build/pl-json.e535cbce.chunk.js +0 -1
  123. package/build/pt-BR-json.e5fafa46.chunk.js +0 -1
  124. package/build/ru-json.866f0ff1.chunk.js +0 -1
  125. package/build/sa-json.7efeb257.chunk.js +0 -1
  126. package/build/sv-json.dc40951f.chunk.js +0 -1
  127. package/build/tr-json.b79eae31.chunk.js +0 -1
  128. package/build/zh-Hans-json.30a18940.chunk.js +0 -1
  129. package/build/zh-json.49d84433.chunk.js +0 -1
@@ -0,0 +1,234 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const { pipe, map, castArray, toNumber } = require('lodash/fp');
5
+ const { stringIncludes } = require('@strapi/utils');
6
+ const { ValidationError } = require('@strapi/utils').errors;
7
+ const { hasSuperAdminRole } = require('../../../server/domain/user');
8
+ const { getService } = require('../../../server/utils');
9
+ const { SUPER_ADMIN_CODE } = require('../../../server/services/constants');
10
+
11
+ /** Checks if ee disabled users list needs to be updated
12
+ * @param {string} id
13
+ * @param {object} input
14
+ */
15
+ const updateEEDisabledUsersList = async (id, input) => {
16
+ const disabledUsers = await getService('seat-enforcement').getDisabledUserList();
17
+
18
+ if (!disabledUsers) {
19
+ return;
20
+ }
21
+
22
+ const user = disabledUsers.find((user) => user.id === Number(id));
23
+ if (!user) {
24
+ return;
25
+ }
26
+
27
+ if (user.isActive !== input.isActive) {
28
+ const newDisabledUsersList = disabledUsers.filter((user) => user.id !== Number(id));
29
+ await strapi.store.set({
30
+ type: 'ee',
31
+ key: 'disabled_users',
32
+ value: newDisabledUsersList,
33
+ });
34
+ }
35
+ };
36
+
37
+ const castNumberArray = pipe(castArray, map(toNumber));
38
+
39
+ const removeFromEEDisabledUsersList = async (ids) => {
40
+ let idsToCheck;
41
+ if (typeof ids === 'object') {
42
+ idsToCheck = castNumberArray(ids);
43
+ } else {
44
+ idsToCheck = [Number(ids)];
45
+ }
46
+
47
+ const disabledUsers = await getService('seat-enforcement').getDisabledUserList();
48
+
49
+ if (!disabledUsers) {
50
+ return;
51
+ }
52
+
53
+ const newDisabledUsersList = disabledUsers.filter((user) => !idsToCheck.includes(user.id));
54
+ await strapi.store.set({
55
+ type: 'ee',
56
+ key: 'disabled_users',
57
+ value: newDisabledUsersList,
58
+ });
59
+ };
60
+
61
+ /**
62
+ * Update a user in database
63
+ * @param id query params to find the user to update
64
+ * @param attributes A partial user object
65
+ * @returns {Promise<user>}
66
+ */
67
+ const updateById = async (id, attributes) => {
68
+ // Check at least one super admin remains
69
+ if (_.has(attributes, 'roles')) {
70
+ const lastAdminUser = await isLastSuperAdminUser(id);
71
+ const superAdminRole = await getService('role').getSuperAdminWithUsersCount();
72
+ const willRemoveSuperAdminRole = !stringIncludes(attributes.roles, superAdminRole.id);
73
+
74
+ if (lastAdminUser && willRemoveSuperAdminRole) {
75
+ throw new ValidationError('You must have at least one user with super admin role.');
76
+ }
77
+ }
78
+
79
+ // cannot disable last super admin
80
+ if (attributes.isActive === false) {
81
+ const lastAdminUser = await isLastSuperAdminUser(id);
82
+ if (lastAdminUser) {
83
+ throw new ValidationError('You must have at least one user with super admin role.');
84
+ }
85
+ }
86
+
87
+ // hash password if a new one is sent
88
+ if (_.has(attributes, 'password')) {
89
+ const hashedPassword = await getService('auth').hashPassword(attributes.password);
90
+
91
+ const updatedUser = await strapi.query('admin::user').update({
92
+ where: { id },
93
+ data: {
94
+ ...attributes,
95
+ password: hashedPassword,
96
+ },
97
+ populate: ['roles'],
98
+ });
99
+
100
+ strapi.eventHub.emit('user.update', { user: sanitizeUser(updatedUser) });
101
+
102
+ return updatedUser;
103
+ }
104
+
105
+ const updatedUser = await strapi.query('admin::user').update({
106
+ where: { id },
107
+ data: attributes,
108
+ populate: ['roles'],
109
+ });
110
+
111
+ await updateEEDisabledUsersList(id, attributes);
112
+
113
+ if (updatedUser) {
114
+ strapi.eventHub.emit('user.update', { user: sanitizeUser(updatedUser) });
115
+ }
116
+
117
+ return updatedUser;
118
+ };
119
+
120
+ /** Delete a user
121
+ * @param id id of the user to delete
122
+ * @returns {Promise<user>}
123
+ */
124
+ const deleteById = async (id) => {
125
+ // Check at least one super admin remains
126
+ const userToDelete = await strapi.query('admin::user').findOne({
127
+ where: { id },
128
+ populate: ['roles'],
129
+ });
130
+
131
+ if (!userToDelete) {
132
+ return null;
133
+ }
134
+
135
+ if (userToDelete) {
136
+ if (userToDelete.roles.some((r) => r.code === SUPER_ADMIN_CODE)) {
137
+ const superAdminRole = await getService('role').getSuperAdminWithUsersCount();
138
+ if (superAdminRole.usersCount === 1) {
139
+ throw new ValidationError('You must have at least one user with super admin role.');
140
+ }
141
+ }
142
+ }
143
+
144
+ const deletedUser = await strapi
145
+ .query('admin::user')
146
+ .delete({ where: { id }, populate: ['roles'] });
147
+
148
+ await removeFromEEDisabledUsersList(id);
149
+
150
+ strapi.eventHub.emit('user.delete', { user: sanitizeUser(deletedUser) });
151
+
152
+ return deletedUser;
153
+ };
154
+
155
+ /** Delete a user
156
+ * @param ids ids of the users to delete
157
+ * @returns {Promise<user>}
158
+ */
159
+ const deleteByIds = async (ids) => {
160
+ // Check at least one super admin remains
161
+ const superAdminRole = await getService('role').getSuperAdminWithUsersCount();
162
+ const nbOfSuperAdminToDelete = await strapi.query('admin::user').count({
163
+ where: {
164
+ id: ids,
165
+ roles: { id: superAdminRole.id },
166
+ },
167
+ });
168
+
169
+ if (superAdminRole.usersCount === nbOfSuperAdminToDelete) {
170
+ throw new ValidationError('You must have at least one user with super admin role.');
171
+ }
172
+
173
+ const deletedUsers = [];
174
+ for (const id of ids) {
175
+ const deletedUser = await strapi.query('admin::user').delete({
176
+ where: { id },
177
+ populate: ['roles'],
178
+ });
179
+
180
+ deletedUsers.push(deletedUser);
181
+ }
182
+
183
+ await removeFromEEDisabledUsersList(ids);
184
+
185
+ strapi.eventHub.emit('user.delete', {
186
+ users: deletedUsers.map((deletedUser) => sanitizeUser(deletedUser)),
187
+ });
188
+
189
+ return deletedUsers;
190
+ };
191
+
192
+ const sanitizeUserRoles = (role) => _.pick(role, ['id', 'name', 'description', 'code']);
193
+
194
+ /**
195
+ * Check if a user is the last super admin
196
+ * @param {int|string} userId user's id to look for
197
+ */
198
+ const isLastSuperAdminUser = async (userId) => {
199
+ const user = await findOne(userId);
200
+ const superAdminRole = await getService('role').getSuperAdminWithUsersCount();
201
+
202
+ return superAdminRole.usersCount === 1 && hasSuperAdminRole(user);
203
+ };
204
+
205
+ /**
206
+ * Remove private user fields
207
+ * @param {Object} user - user to sanitize
208
+ */
209
+ const sanitizeUser = (user) => {
210
+ return {
211
+ ..._.omit(user, ['password', 'resetPasswordToken', 'registrationToken', 'roles']),
212
+ roles: user.roles && user.roles.map(sanitizeUserRoles),
213
+ };
214
+ };
215
+
216
+ /**
217
+ * Find one user
218
+ */
219
+ const findOne = async (id, populate = ['roles']) => {
220
+ return strapi.entityService.findOne('admin::user', id, { populate });
221
+ };
222
+
223
+ const getCurrentActiveUserCount = async () => {
224
+ return strapi.db.query('admin::user').count({ where: { isActive: true } });
225
+ };
226
+
227
+ module.exports = {
228
+ updateEEDisabledUsersList,
229
+ removeFromEEDisabledUsersList,
230
+ getCurrentActiveUserCount,
231
+ deleteByIds,
232
+ deleteById,
233
+ updateById,
234
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/admin",
3
- "version": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
3
+ "version": "4.7.0",
4
4
  "description": "Strapi Admin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,15 +46,15 @@
46
46
  "@casl/ability": "^5.4.3",
47
47
  "@fingerprintjs/fingerprintjs": "3.3.6",
48
48
  "@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
49
- "@strapi/babel-plugin-switch-ee-ce": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
50
- "@strapi/data-transfer": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
49
+ "@strapi/babel-plugin-switch-ee-ce": "4.7.0",
50
+ "@strapi/data-transfer": "4.7.0",
51
51
  "@strapi/design-system": "1.6.3",
52
- "@strapi/helper-plugin": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
52
+ "@strapi/helper-plugin": "4.7.0",
53
53
  "@strapi/icons": "1.6.3",
54
- "@strapi/permissions": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
55
- "@strapi/provider-audit-logs-local": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
56
- "@strapi/typescript-utils": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
57
- "@strapi/utils": "4.7.0-exp.117579f4c13806c2cd518e7d7d2f9d0c8a20107d",
54
+ "@strapi/permissions": "4.7.0",
55
+ "@strapi/provider-audit-logs-local": "4.7.0",
56
+ "@strapi/typescript-utils": "4.7.0",
57
+ "@strapi/utils": "4.7.0",
58
58
  "axios": "1.2.2",
59
59
  "babel-loader": "^9.1.2",
60
60
  "babel-plugin-styled-components": "2.0.2",
@@ -165,5 +165,5 @@
165
165
  }
166
166
  }
167
167
  },
168
- "gitHead": "117579f4c13806c2cd518e7d7d2f9d0c8a20107d"
168
+ "gitHead": "948dbb3121330ffd43f3a4f55522c797770e0b2a"
169
169
  }
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const { getService } = require('../utils');
4
+
5
+ module.exports = () => async (ctx, next) => {
6
+ const transferUtils = getService('transfer').utils;
7
+
8
+ const { hasValidTokenSalt, isDataTransferEnabled, isDisabledFromEnv } = transferUtils;
9
+
10
+ if (isDataTransferEnabled()) {
11
+ return next();
12
+ }
13
+
14
+ if (!hasValidTokenSalt()) {
15
+ return ctx.notImplemented(
16
+ 'The server configuration for data transfer is invalid. Please contact your server administrator.'
17
+ );
18
+ }
19
+
20
+ if (isDisabledFromEnv()) {
21
+ return ctx.notFound();
22
+ }
23
+
24
+ // This should never happen as long as we're handling individual scenarios above
25
+ throw new Error('Unexpected error while trying to access a data transfer route');
26
+ };
@@ -4,4 +4,5 @@ const rateLimit = require('./rateLimit');
4
4
 
5
5
  module.exports = {
6
6
  rateLimit,
7
+ 'data-transfer': require('./data-transfer'),
7
8
  };
@@ -9,15 +9,7 @@ module.exports = [
9
9
  path: '/transfer/runner/connect',
10
10
  handler: 'transfer.runner-connect',
11
11
  config: {
12
- middlewares: [
13
- (ctx, next) => {
14
- if (process.env.STRAPI_DISABLE_REMOTE_DATA_TRANSFER === 'true') {
15
- return ctx.notFound();
16
- }
17
-
18
- return next();
19
- },
20
- ],
12
+ middlewares: ['admin::data-transfer'],
21
13
  // TODO: Allow not passing any scope <> Add a way to prevent assigning one by default
22
14
  auth: { strategies: [dataTransferAuthStrategy], scope: ['push'] },
23
15
  },
@@ -28,6 +20,7 @@ module.exports = [
28
20
  path: '/transfer/tokens',
29
21
  handler: 'transfer.token-create',
30
22
  config: {
23
+ middlewares: ['admin::data-transfer'],
31
24
  policies: [
32
25
  'admin::isAuthenticatedAdmin',
33
26
  { name: 'admin::hasPermissions', config: { actions: ['admin::transfer.tokens.create'] } },
@@ -39,6 +32,7 @@ module.exports = [
39
32
  path: '/transfer/tokens',
40
33
  handler: 'transfer.token-list',
41
34
  config: {
35
+ middlewares: ['admin::data-transfer'],
42
36
  policies: [
43
37
  'admin::isAuthenticatedAdmin',
44
38
  { name: 'admin::hasPermissions', config: { actions: ['admin::transfer.tokens.read'] } },
@@ -50,6 +44,7 @@ module.exports = [
50
44
  path: '/transfer/tokens/:id',
51
45
  handler: 'transfer.token-revoke',
52
46
  config: {
47
+ middlewares: ['admin::data-transfer'],
53
48
  policies: [
54
49
  'admin::isAuthenticatedAdmin',
55
50
  { name: 'admin::hasPermissions', config: { actions: ['admin::transfer.tokens.delete'] } },
@@ -61,6 +56,7 @@ module.exports = [
61
56
  path: '/transfer/tokens/:id',
62
57
  handler: 'transfer.token-getById',
63
58
  config: {
59
+ middlewares: ['admin::data-transfer'],
64
60
  policies: [
65
61
  'admin::isAuthenticatedAdmin',
66
62
  { name: 'admin::hasPermissions', config: { actions: ['admin::transfer.tokens.read'] } },
@@ -72,6 +68,7 @@ module.exports = [
72
68
  path: '/transfer/tokens/:id',
73
69
  handler: 'transfer.token-update',
74
70
  config: {
71
+ middlewares: ['admin::data-transfer'],
75
72
  policies: [
76
73
  'admin::isAuthenticatedAdmin',
77
74
  { name: 'admin::hasPermissions', config: { actions: ['admin::transfer.tokens.update'] } },
@@ -83,6 +80,7 @@ module.exports = [
83
80
  path: '/transfer/tokens/:id/regenerate',
84
81
  handler: 'transfer.token-regenerate',
85
82
  config: {
83
+ middlewares: ['admin::data-transfer'],
86
84
  policies: [
87
85
  'admin::isAuthenticatedAdmin',
88
86
  {
@@ -3,4 +3,5 @@
3
3
  module.exports = {
4
4
  permission: require('./permission'),
5
5
  token: require('./token'),
6
+ utils: require('./utils'),
6
7
  };
@@ -8,6 +8,7 @@ const {
8
8
  } = require('@strapi/utils');
9
9
 
10
10
  const constants = require('../constants');
11
+ const { getService } = require('../../utils');
11
12
 
12
13
  const TRANSFER_TOKEN_UID = 'admin::transfer-token';
13
14
  const TRANSFER_TOKEN_PERMISSION_UID = 'admin::transfer-token-permission';
@@ -327,6 +328,12 @@ const getExpirationFields = (lifespan) => {
327
328
  * @returns {string}
328
329
  */
329
330
  const hash = (accessKey) => {
331
+ const { hasValidTokenSalt } = getService('transfer').utils;
332
+
333
+ if (!hasValidTokenSalt()) {
334
+ throw new TypeError('Required token salt is not defined');
335
+ }
336
+
330
337
  return crypto
331
338
  .createHmac('sha512', strapi.config.get('admin.transfer.token.salt'))
332
339
  .update(accessKey)
@@ -337,9 +344,17 @@ const hash = (accessKey) => {
337
344
  * @returns {void}
338
345
  */
339
346
  const checkSaltIsDefined = () => {
340
- if (!strapi.config.get('admin.transfer.token.salt')) {
341
- throw new Error(
342
- `Missing transfer.token.salt. Please set transfer.token.salt in config/admin.js (ex: you can generate one using Node with \`crypto.randomBytes(16).toString('base64')\`).
347
+ const { hasValidTokenSalt, isDisabledFromEnv } = getService('transfer').utils;
348
+
349
+ // Ignore the check if the data-transfer feature is manually disabled
350
+ if (isDisabledFromEnv()) {
351
+ return;
352
+ }
353
+
354
+ if (!hasValidTokenSalt()) {
355
+ process.emitWarning(
356
+ `Missing transfer.token.salt: Data transfer features have been disabled.
357
+ Please set transfer.token.salt in config/admin.js (ex: you can generate one using Node with \`crypto.randomBytes(16).toString('base64')\`)
343
358
  For security reasons, prefer storing the secret in an environment variable and read it in config/admin.js. See https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#configuration-using-environment-variables.`
344
359
  );
345
360
  }
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const { env } = require('@strapi/utils');
4
+
5
+ const { getService } = require('../../utils');
6
+
7
+ /**
8
+ * Returns whether the data transfer features have been disabled from the env configuration
9
+ *
10
+ * @returns {boolean}
11
+ */
12
+ const isDisabledFromEnv = () => {
13
+ return env.bool('STRAPI_DISABLE_REMOTE_DATA_TRANSFER', false);
14
+ };
15
+
16
+ /**
17
+ * A valid transfer token salt must be a non-empty string defined in the Strapi config
18
+ *
19
+ * @returns {boolean}
20
+ */
21
+ const hasValidTokenSalt = () => {
22
+ const salt = strapi.config.get('admin.transfer.token.salt', null);
23
+
24
+ return typeof salt === 'string' && salt.length > 0;
25
+ };
26
+
27
+ /**
28
+ * Checks whether data transfer features are enabled
29
+ *
30
+ * @returns {boolean}
31
+ */
32
+ const isDataTransferEnabled = () => {
33
+ const { utils } = getService('transfer');
34
+
35
+ return !utils.isDisabledFromEnv() && utils.hasValidTokenSalt();
36
+ };
37
+
38
+ module.exports = { isDataTransferEnabled, isDisabledFromEnv, hasValidTokenSalt };
@@ -1,95 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { useIntl } from 'react-intl';
4
- import { usePersistentState } from '@strapi/helper-plugin';
5
- import { Select, Option, Typography } from '@strapi/design-system';
6
- import { getDateOfExpiration } from '../../../pages/ApiTokens/EditView/utils';
7
-
8
- const LifeSpanInput = ({ token, errors, values, onChange, disabled }) => {
9
- const { formatMessage } = useIntl();
10
- const [lang] = usePersistentState('strapi-admin-language', 'en');
11
-
12
- return (
13
- <>
14
- <Select
15
- name="lifespan"
16
- label={formatMessage({
17
- id: 'Settings.apiTokens.form.duration',
18
- defaultMessage: 'Token duration',
19
- })}
20
- value={values.lifespan !== null ? values.lifespan : '0'}
21
- error={
22
- errors.lifespan
23
- ? formatMessage(
24
- errors.lifespan?.id
25
- ? errors.lifespan
26
- : { id: errors.lifespan, defaultMessage: errors.lifespan }
27
- )
28
- : null
29
- }
30
- onChange={(value) => {
31
- onChange({ target: { name: 'lifespan', value } });
32
- }}
33
- required
34
- disabled={disabled}
35
- placeholder="Select"
36
- >
37
- <Option value="604800000">
38
- {formatMessage({
39
- id: 'Settings.apiTokens.duration.7-days',
40
- defaultMessage: '7 days',
41
- })}
42
- </Option>
43
- <Option value="2592000000">
44
- {formatMessage({
45
- id: 'Settings.apiTokens.duration.30-days',
46
- defaultMessage: '30 days',
47
- })}
48
- </Option>
49
- <Option value="7776000000">
50
- {formatMessage({
51
- id: 'Settings.apiTokens.duration.90-days',
52
- defaultMessage: '90 days',
53
- })}
54
- </Option>
55
- <Option value="0">
56
- {formatMessage({
57
- id: 'Settings.apiTokens.duration.unlimited',
58
- defaultMessage: 'Unlimited',
59
- })}
60
- </Option>
61
- </Select>
62
- <Typography variant="pi" textColor="neutral600">
63
- {disabled &&
64
- `${formatMessage({
65
- id: 'Settings.apiTokens.duration.expiration-date',
66
- defaultMessage: 'Expiration date',
67
- })}: ${getDateOfExpiration(token?.createdAt, parseInt(values.lifespan, 10, lang))}`}
68
- </Typography>
69
- </>
70
- );
71
- };
72
-
73
- LifeSpanInput.propTypes = {
74
- errors: PropTypes.string,
75
- onChange: PropTypes.func.isRequired,
76
- values: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
77
- disabled: PropTypes.bool.isRequired,
78
- token: PropTypes.shape({
79
- id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
80
- type: PropTypes.string,
81
- lifespan: PropTypes.string,
82
- name: PropTypes.string,
83
- accessKey: PropTypes.string,
84
- permissions: PropTypes.array,
85
- description: PropTypes.string,
86
- createdAt: PropTypes.string,
87
- }),
88
- };
89
-
90
- LifeSpanInput.defaultProps = {
91
- errors: {},
92
- token: {},
93
- };
94
-
95
- export default LifeSpanInput;
@@ -1,30 +0,0 @@
1
- (self.webpackChunk_strapi_admin=self.webpackChunk_strapi_admin||[]).push([[4649],{30493:function(b,M,n){"use strict";n.d(M,{Z:function(){return s}});var e=n(32735),g=n(5636),a=n(60216),t=n.n(a),u=n(13478),A=n(57269),E=n(39161),f=n(8888);const C=({onRegenerate:y,idToRegenerate:c,backUrl:R})=>{const{formatMessage:I}=(0,g.useIntl)(),[_,L]=(0,e.useState)(!1),{regenerateData:W,isLoadingConfirmation:G}=(0,f.rW)(R,c,y),N=async()=>{W(),L(!1)};return e.createElement(e.Fragment,null,e.createElement(E.Button,{startIcon:e.createElement(A.Refresh,null),type:"button",size:"S",variant:"tertiary",onClick:()=>L(!0),name:"regenerate"},I({id:"Settings.apiTokens.regenerate",defaultMessage:"Regenerate"})),e.createElement(u.ConfirmDialog,{bodyText:{id:"Settings.apiTokens.popUpWarning.message",defaultMessage:"Are you sure you want to regenerate this token?"},iconRightButton:e.createElement(A.Refresh,null),isConfirmButtonLoading:G,isOpen:_,onToggleDialog:()=>L(!1),onConfirm:N,leftButtonText:{id:"Settings.apiTokens.Button.cancel",defaultMessage:"Cancel"},rightButtonText:{id:"Settings.apiTokens.Button.regenerate",defaultMessage:"Regenerate"},title:{id:"Settings.apiTokens.RegenerateDialog.title",defaultMessage:"Regenerate token"}}))};C.defaultProps={onRegenerate(){}},C.propTypes={onRegenerate:t().func,idToRegenerate:t().oneOfType([t().number,t().string]).isRequired,backUrl:t().string.isRequired};var h=C;const v=({title:y,token:c,setToken:R,canEditInputs:I,canRegenerate:_,isSubmitting:L,backUrl:W,regenerateUrl:G})=>{const{formatMessage:N}=(0,g.useIntl)(),Z=X=>{R({...c,accessKey:X})};return e.createElement(E.HeaderLayout,{title:c?.name||N(y),primaryAction:I?e.createElement(E.Stack,{horizontal:!0,spacing:2},_&&c?.id&&e.createElement(h,{backUrl:G,onRegenerate:Z,idToRegenerate:c?.id}),e.createElement(E.Button,{disabled:L,loading:L,startIcon:e.createElement(A.Check,null),type:"submit",size:"S"},N({id:"global.save",defaultMessage:"Save"}))):_&&c?.id&&e.createElement(h,{onRegenerate:Z,idToRegenerate:c?.id,backUrl:G}),navigationAction:e.createElement(u.Link,{startIcon:e.createElement(A.ArrowLeft,null),to:W},N({id:"global.back",defaultMessage:"Back"}))})};v.propTypes={token:t().shape({id:t().oneOfType([t().number,t().string]),type:t().string,lifespan:t().oneOfType([t().number,t().string]),name:t().string,accessKey:t().string,permissions:t().array,description:t().string,createdAt:t().string}),canEditInputs:t().bool.isRequired,canRegenerate:t().bool.isRequired,setToken:t().func.isRequired,isSubmitting:t().bool.isRequired,backUrl:t().string.isRequired,title:t().shape({id:t().string,label:t().string}).isRequired,regenerateUrl:t().string.isRequired},v.defaultProps={token:void 0};var s=v},4321:function(b,M,n){"use strict";var e=n(32735),g=n(60216),a=n.n(g),t=n(5636),u=n(39161),A=n.n(u),E=n(37944);const f=({token:C,errors:h,values:v,onChange:s,isCreating:y})=>{const{formatMessage:c}=(0,t.useIntl)();return e.createElement(e.Fragment,null,e.createElement(u.Select,{name:"lifespan",label:c({id:"Settings.apiTokens.form.duration",defaultMessage:"Token duration"}),value:v.lifespan!==null?v.lifespan:"0",error:h.lifespan?c(h.lifespan?.id?h.lifespan:{id:h.lifespan,defaultMessage:h.lifespan}):null,onChange:R=>{s({target:{name:"lifespan",value:R}})},required:!0,disabled:!y,placeholder:"Select"},e.createElement(u.Option,{value:"604800000"},c({id:"Settings.tokens.duration.7-days",defaultMessage:"7 days"})),e.createElement(u.Option,{value:"2592000000"},c({id:"Settings.tokens.duration.30-days",defaultMessage:"30 days"})),e.createElement(u.Option,{value:"7776000000"},c({id:"Settings.tokens.duration.90-days",defaultMessage:"90 days"})),e.createElement(u.Option,{value:"0"},c({id:"Settings.tokens.duration.unlimited",defaultMessage:"Unlimited"}))),e.createElement(u.Typography,{variant:"pi",textColor:"neutral600"},!y&&`${c({id:"Settings.tokens.duration.expiration-date",defaultMessage:"Expiration date"})}: ${(0,E.IX)(C?.createdAt,parseInt(v.lifespan,10))}`))};f.propTypes={errors:a().shape({lifespan:a().string}),onChange:a().func.isRequired,values:a().shape({lifespan:a().oneOfType([a().number,a().string])}).isRequired,isCreating:a().bool.isRequired,token:a().shape({id:a().oneOfType([a().number,a().string]),type:a().string,lifespan:a().string,name:a().string,accessKey:a().string,permissions:a().array,description:a().string,createdAt:a().string})},f.defaultProps={errors:{},token:{}},M.Z=f},93682:function(b,M,n){"use strict";var e=n(32735),g=n(5636),a=n(13478),t=n.n(a),u=n(39161),A=n.n(u),E=n(57269),f=n.n(E),C=n(60216),h=n.n(C),v=n(59087),s=n.n(v);const y=({token:c,tokenType:R})=>{const{formatMessage:I}=(0,g.useIntl)(),_=(0,a.useNotification)(),{trackUsage:L}=(0,a.useTracking)(),W=(0,e.useRef)(L);return e.createElement(a.ContentBox,{endAction:c&&e.createElement("span",{style:{alignSelf:"start"}},e.createElement(v.CopyToClipboard,{onCopy:()=>{W.current("didCopyTokenKey",{tokenType:R}),_({type:"success",message:{id:"Settings.tokens.notification.copied"}})},text:c},e.createElement(u.IconButton,{label:I({id:"app.component.CopyToClipboard.label",defaultMessage:"Copy to clipboard"}),noBorder:!0,icon:e.createElement(E.Duplicate,null),style:{padding:0,height:"1rem"}}))),title:c||I({id:"Settings.tokens.copy.editTitle",defaultMessage:"This token isn\u2019t accessible anymore."}),subtitle:I(c?{id:"Settings.tokens.copy.lastWarning",defaultMessage:"Make sure to copy this token, you won\u2019t be able to see it again!"}:{id:"Settings.tokens.copy.editMessage",defaultMessage:"For security reasons, you can only see your token once."}),icon:e.createElement(E.Key,null),iconBackground:"neutral100"})};y.defaultProps={token:null},y.propTypes={token:h().string,tokenType:h().string.isRequired},M.Z=y},42789:function(b,M,n){"use strict";var e=n(32735),g=n(60216),a=n.n(g),t=n(5636),u=n(39161),A=n.n(u);const E=({errors:f,values:C,onChange:h,canEditInputs:v})=>{const{formatMessage:s}=(0,t.useIntl)();return e.createElement(u.Textarea,{label:s({id:"Settings.tokens.form.description",defaultMessage:"Description"}),name:"description",error:f.description?s(f.description?.id?f.description:{id:f.description,defaultMessage:f.description}):null,onChange:h,disabled:!v},C.description)};E.propTypes={errors:a().shape({description:a().string}),onChange:a().func.isRequired,canEditInputs:a().bool.isRequired,values:a().shape({description:a().string}).isRequired},E.defaultProps={errors:{}},M.Z=E},8377:function(b,M,n){"use strict";var e=n(32735),g=n(60216),a=n.n(g),t=n(5636),u=n(39161),A=n.n(u);const E=({errors:f,values:C,onChange:h,canEditInputs:v})=>{const{formatMessage:s}=(0,t.useIntl)();return e.createElement(u.TextInput,{name:"name",error:f.name?s(f.name?.id?f.name:{id:f.name,defaultMessage:f.name}):null,label:s({id:"Settings.tokens.form.name",defaultMessage:"Name"}),onChange:h,value:C.name,disabled:!v,required:!0})};E.propTypes={errors:a().shape({name:a().string}),onChange:a().func.isRequired,canEditInputs:a().bool.isRequired,values:a().shape({name:a().string}).isRequired},E.defaultProps={errors:{}},M.Z=E},68774:function(b,M,n){"use strict";n.d(M,{Z:function(){return e},f:function(){return g}});const e="api-token",g="transfer-token"},24649:function(b,M,n){"use strict";n.d(M,{Z:function(){return De}});var e=n(32735),g=n(5636),a=n(13478),t=n(39161),u=n(83281),A=n(5141),E=n(84968),f=n(64421),C=n(37944),h=n(57269),v=n(60216),s=n.n(v);const y=({apiTokenName:o})=>{const{formatMessage:i}=(0,g.useIntl)();return(0,a.useFocusWhenNavigate)(),e.createElement(t.Main,{"aria-busy":"true"},e.createElement(a.SettingsPageTitle,{name:"API Tokens"}),e.createElement(t.HeaderLayout,{primaryAction:e.createElement(t.Button,{disabled:!0,startIcon:e.createElement(h.Check,null),type:"button",size:"L"},i({id:"global.save",defaultMessage:"Save"})),title:o||i({id:"Settings.apiTokens.createPage.title",defaultMessage:"Create API Token"})}),e.createElement(t.ContentLayout,null,e.createElement(a.LoadingIndicatorPage,null)))};y.defaultProps={apiTokenName:null},y.propTypes={apiTokenName:s().string};var c=y,R=n(72041);const I=(0,e.createContext)({}),_=({children:o,...i})=>e.createElement(I.Provider,{value:i},o),L=()=>(0,e.useContext)(I);_.propTypes={children:s().node.isRequired};var G=(o,i=[])=>({...o,selectedAction:null,routes:[],selectedActions:[],data:(0,C.mk)(i)}),N=n(97889),Z=n(92891);const X={data:{},selectedActions:[]};var re=(o,i)=>(0,N.default)(o,r=>{switch(i.type){case"ON_CHANGE":{r.selectedActions.includes(i.value)?(0,Z.pull)(r.selectedActions,i.value):r.selectedActions.push(i.value);break}case"SELECT_ALL_IN_PERMISSION":{i.value.every(d=>r.selectedActions.includes(d.actionId))?i.value.forEach(d=>{(0,Z.pull)(r.selectedActions,d.actionId)}):i.value.forEach(d=>{r.selectedActions.push(d.actionId)});break}case"SELECT_ALL_ACTIONS":{r.selectedActions=[...r.data.allActionsIds];break}case"ON_CHANGE_READ_ONLY":{const T=r.data.allActionsIds.filter(d=>d.includes("find")||d.includes("findOne"));r.selectedActions=[...T];break}case"UPDATE_PERMISSIONS_LAYOUT":{r.data=(0,C.mk)(i.value);break}case"UPDATE_ROUTES":{r.routes={...i.value};break}case"UPDATE_PERMISSIONS":{r.selectedActions=[...i.value];break}case"SET_SELECTED_ACTION":{r.selectedAction=i.value;break}default:return r}}),z=n(83292);const ie=z.css`
2
- background: ${o=>o.theme.colors.primary100};
3
- svg {
4
- opacity: 1;
5
- }
6
- `;var le=(0,z.default)(t.Box)`
7
- display: flex;
8
- justify-content: space-between;
9
- align-items: center;
10
-
11
- svg {
12
- opacity: 0;
13
- path {
14
- fill: ${o=>o.theme.colors.primary600};
15
- }
16
- }
17
-
18
- /* Show active style both on hover and when the action is selected */
19
- ${o=>o.isActive&&ie}
20
- &:hover {
21
- ${ie}
22
- }
23
- `;const ce=z.default.div`
24
- flex: 1;
25
- align-self: center;
26
- border-top: 1px solid ${({theme:o})=>o.colors.neutral150};
27
- `,J=({controllers:o,label:i,orderNumber:r,disabled:T,onExpanded:d,indexExpandendCollapsedContent:l})=>{const{value:{onChangeSelectAll:k,onChange:B,selectedActions:P,setSelectedAction:U,selectedAction:$}}=L(),[x,j]=(0,e.useState)(!1),{formatMessage:H}=(0,g.useIntl)(),S=()=>{j(D=>!D),d(r)};(0,e.useEffect)(()=>{l!==null&&l!==r&&x&&j(!1)},[l,r,x]);const F=D=>D===$;return e.createElement(t.Accordion,{expanded:x,onToggle:S,variant:r%2?"primary":"secondary"},e.createElement(t.AccordionToggle,{title:(0,Z.capitalize)(i)}),e.createElement(t.AccordionContent,null,o?.map(D=>{const Q=D.actions.every(m=>P.includes(m.actionId)),ne=D.actions.some(m=>P.includes(m.actionId));return e.createElement(t.Box,{key:`${i}.${D?.controller}`},e.createElement(t.Flex,{justifyContent:"space-between",alignItems:"center",padding:4},e.createElement(t.Box,{paddingRight:4},e.createElement(t.Typography,{variant:"sigma",textColor:"neutral600"},D?.controller)),e.createElement(ce,null),e.createElement(t.Box,{paddingLeft:4},e.createElement(t.Checkbox,{value:Q,indeterminate:!Q&&ne,onValueChange:()=>{k({target:{value:[...D.actions]}})},disabled:T},H({id:"app.utils.select-all",defaultMessage:"Select all"})))),e.createElement(t.Grid,{gap:4,padding:4},D?.actions&&D?.actions.map(m=>e.createElement(t.GridItem,{col:6,key:m.actionId},e.createElement(le,{isActive:F(m.actionId),padding:2,hasRadius:!0},e.createElement(t.Checkbox,{value:P.includes(m.actionId),name:m.actionId,onValueChange:()=>{B({target:{value:m.actionId}})},disabled:T},m.action),e.createElement("button",{type:"button","data-testid":"action-cog",onClick:()=>U({target:{value:m.actionId}}),style:{display:"inline-flex",alignItems:"center"}},e.createElement(h.Cog,null)))))))})))};J.defaultProps={controllers:[],orderNumber:0,disabled:!1,onExpanded:()=>null,indexExpandendCollapsedContent:null},J.propTypes={controllers:s().array,orderNumber:s().number,label:s().string.isRequired,disabled:s().bool,onExpanded:s().func,indexExpandendCollapsedContent:s().number};var de=J;const w=({section:o,...i})=>{const[r,T]=(0,e.useState)(null),d=l=>T(l);return e.createElement(t.Box,{padding:4,background:"neutral0"},o&&o.map((l,k)=>e.createElement(de,{key:l.apiId,label:l.label,controllers:l.controllers,orderNumber:k,indexExpandendCollapsedContent:r,onExpanded:d,name:l.apiId,...i})))};w.defaultProps={section:null},w.propTypes={section:s().arrayOf(s().object)};var pe=w,ue=n(37213),ge=n.n(ue),me=n(15738),Ee=n.n(me),fe=o=>{switch(o){case"POST":return{text:"success600",border:"success200",background:"success100"};case"GET":return{text:"secondary600",border:"secondary200",background:"secondary100"};case"PUT":return{text:"warning600",border:"warning200",background:"warning100"};case"DELETE":return{text:"danger600",border:"danger200",background:"danger100"};default:return{text:"neutral600",border:"neutral200",background:"neutral100"}}};const ye=(0,z.default)(t.Box)`
28
- margin: -1px;
29
- border-radius: ${({theme:o})=>o.spaces[1]} 0 0 ${({theme:o})=>o.spaces[1]};
30
- `;function q({route:o}){const{formatMessage:i}=(0,g.useIntl)(),{method:r,handler:T,path:d}=o,l=d?Ee()(d.split("/")):[],[k="",B=""]=T?T.split("."):[],P=fe(o.method);return e.createElement(t.Stack,{spacing:2},e.createElement(t.Typography,{variant:"delta",as:"h3"},i({id:"Settings.apiTokens.createPage.BoundRoute.title",defaultMessage:"Bound route to"}),"\xA0",e.createElement("span",null,k),e.createElement(t.Typography,{variant:"delta",textColor:"primary600"},".",B)),e.createElement(t.Stack,{horizontal:!0,hasRadius:!0,background:"neutral0",borderColor:"neutral200",spacing:0},e.createElement(ye,{background:P.background,borderColor:P.border,padding:2},e.createElement(t.Typography,{fontWeight:"bold",textColor:P.text},r)),e.createElement(t.Box,{paddingLeft:2,paddingRight:2},ge()(l,U=>e.createElement(t.Typography,{key:U,textColor:U.includes(":")?"neutral600":"neutral900"},"/",U)))))}q.defaultProps={route:{handler:"Nocontroller.error",method:"GET",path:"/there-is-no-path"}},q.propTypes={route:s().shape({handler:s().string,method:s().string,path:s().string})};var Te=q,he=()=>{const{value:{selectedAction:o,routes:i}}=L(),{formatMessage:r}=(0,g.useIntl)(),T=o?.split(".")[0];return e.createElement(t.GridItem,{col:5,background:"neutral150",paddingTop:6,paddingBottom:6,paddingLeft:7,paddingRight:7,style:{minHeight:"100%"}},o?e.createElement(t.Stack,{spacing:2},i[T]?.map(d=>d.config.auth?.scope?.includes(o)||d.handler===o?e.createElement(Te,{key:d.handler,route:d}):null)):e.createElement(t.Stack,{spacing:2},e.createElement(t.Typography,{variant:"delta",as:"h3"},r({id:"Settings.apiTokens.createPage.permissions.header.title",defaultMessage:"Advanced settings"})),e.createElement(t.Typography,{as:"p",textColor:"neutral600"},r({id:"Settings.apiTokens.createPage.permissions.header.hint",defaultMessage:"Select the application's actions or the plugin's actions and click on the cog icon to display the bound route"}))))};const Ce=({...o})=>{const{value:{data:i}}=L(),{formatMessage:r}=(0,g.useIntl)();return e.createElement(t.Grid,{gap:0,shadow:"filterShadow",hasRadius:!0,background:"neutral0"},e.createElement(t.GridItem,{col:7,paddingTop:6,paddingBottom:6,paddingLeft:7,paddingRight:7},e.createElement(t.Stack,{spacing:2},e.createElement(t.Typography,{variant:"delta",as:"h2"},r({id:"Settings.apiTokens.createPage.permissions.title",defaultMessage:"Permissions"})),e.createElement(t.Typography,{as:"p",textColor:"neutral600"},r({id:"Settings.apiTokens.createPage.permissions.description",defaultMessage:"Only actions bound by a route are listed below."}))),i?.permissions&&e.createElement(pe,{section:i?.permissions,...o})),e.createElement(he,null))};var ve=(0,e.memo)(Ce),Ae=n(4321),Me=n(8377),ke=n(42789);const ee=({errors:o,values:i,onChange:r,canEditInputs:T,options:d,label:l})=>{const{formatMessage:k}=(0,g.useIntl)();return e.createElement(t.Select,{name:"type",label:k({id:l.id,defaultMessage:l.defaultMessage}),value:i?.type,error:o.type?k(o.type?.id?o.type:{id:o.type,defaultMessage:o.type}):null,onChange:r,placeholder:"Select",required:!0,disabled:!T},d&&d.map(({value:B,label:P})=>e.createElement(t.Option,{key:B,value:B},k(P))))};ee.propTypes={options:s().arrayOf(s().shape({label:s().shape({id:s().string,defaultMessage:s().string}),value:s().string})),errors:s().shape({type:s().string}),onChange:s().func.isRequired,canEditInputs:s().bool.isRequired,values:s().shape({type:s().string}).isRequired,label:s().shape({id:s().string,defaultMessage:s().string}).isRequired},ee.defaultProps={errors:{},options:[]};var Re=ee;const te=({errors:o,onChange:i,canEditInputs:r,isCreating:T,values:d,apiToken:l,onDispatch:k,setHasChangedPermissions:B})=>{const{formatMessage:P}=(0,g.useIntl)(),U=({target:{value:x}})=>{B(!1),x==="full-access"&&k({type:"SELECT_ALL_ACTIONS"}),x==="read-only"&&k({type:"ON_CHANGE_READ_ONLY"})},$=[{value:"read-only",label:{id:"Settings.apiTokens.types.read-only",defaultMessage:"Read-only"}},{value:"full-access",label:{id:"Settings.apiTokens.types.full-access",defaultMessage:"Full access"}},{value:"custom",label:{id:"Settings.apiTokens.types.custom",defaultMessage:"Custom"}}];return e.createElement(t.Box,{background:"neutral0",hasRadius:!0,shadow:"filterShadow",paddingTop:6,paddingBottom:6,paddingLeft:7,paddingRight:7},e.createElement(t.Stack,{spacing:4},e.createElement(t.Typography,{variant:"delta",as:"h2"},P({id:"global.details",defaultMessage:"Details"})),e.createElement(t.Grid,{gap:5},e.createElement(t.GridItem,{key:"name",col:6,xs:12},e.createElement(Me.Z,{errors:o,values:d,canEditInputs:r,onChange:i})),e.createElement(t.GridItem,{key:"description",col:6,xs:12},e.createElement(ke.Z,{errors:o,values:d,canEditInputs:r,onChange:i})),e.createElement(t.GridItem,{key:"lifespan",col:6,xs:12},e.createElement(Ae.Z,{isCreating:T,errors:o,values:d,onChange:i,token:l})),e.createElement(t.GridItem,{key:"type",col:6,xs:12},e.createElement(Re,{values:d,errors:o,label:{id:"Settings.apiTokens.form.type",defaultMessage:"Token type"},onChange:x=>{U({target:{value:x}}),i({target:{name:"type",value:x}})},options:$,canEditInputs:r})))))};te.propTypes={errors:s().shape({name:s().string,description:s().string,lifespan:s().string,type:s().string}),onChange:s().func.isRequired,canEditInputs:s().bool.isRequired,values:s().shape({name:s().string,description:s().string,lifespan:s().oneOfType([s().number,s().string]),type:s().string}).isRequired,isCreating:s().bool.isRequired,apiToken:s().shape({id:s().oneOfType([s().number,s().string]),type:s().string,lifespan:s().string,name:s().string,accessKey:s().string,permissions:s().array,description:s().string,createdAt:s().string}),onDispatch:s().func.isRequired,setHasChangedPermissions:s().func.isRequired},te.defaultProps={errors:{},apiToken:{}};var Pe=te,Oe=n(93682),Ie=n(30493),Y=n(68774);const Se="Name already taken";var De=()=>{(0,a.useFocusWhenNavigate)();const{formatMessage:o}=(0,g.useIntl)(),{lockApp:i,unlockApp:r}=(0,a.useOverlayBlocker)(),T=(0,a.useNotification)(),d=(0,A.useHistory)(),[l,k]=(0,e.useState)(d.location.state?.apiToken.accessKey?{...d.location.state.apiToken}:null),{trackUsage:B}=(0,a.useTracking)(),P=(0,e.useRef)(B),{setCurrentStep:U}=(0,a.useGuidedTour)(),{allowedActions:{canCreate:$,canUpdate:x,canRegenerate:j}}=(0,a.useRBAC)(R.Z.settings["api-tokens"]),[H,S]=(0,e.useReducer)(re,X,p=>G(p,{})),{params:{id:F}}=(0,A.useRouteMatch)("/settings/api-tokens/:id"),{get:D,post:Q,put:ne}=(0,a.useFetchClient)(),m=F==="create";(0,E.useQuery)("content-api-permissions",async()=>{const[p,K]=await Promise.all(["/admin/content-api/permissions","/admin/content-api/routes"].map(async V=>{const{data:O}=await D(V);return O.data}));S({type:"UPDATE_PERMISSIONS_LAYOUT",value:p}),S({type:"UPDATE_ROUTES",value:K}),l&&(l?.type==="read-only"&&S({type:"ON_CHANGE_READ_ONLY"}),l?.type==="full-access"&&S({type:"SELECT_ALL_ACTIONS"}),l?.type==="custom"&&S({type:"UPDATE_PERMISSIONS",value:l?.permissions}))},{onError(){T({type:"warning",message:{id:"notification.error",defaultMessage:"An error occured"}})}}),(0,e.useEffect)(()=>{P.current(m?"didAddTokenFromList":"didEditTokenFromList",{tokenType:Y.Z})},[m]);const{status:_e}=(0,E.useQuery)(["api-token",F],async()=>{const{data:{data:p}}=await D(`/admin/api-tokens/${F}`);return k({...p}),p?.type==="read-only"&&S({type:"ON_CHANGE_READ_ONLY"}),p?.type==="full-access"&&S({type:"SELECT_ALL_ACTIONS"}),p?.type==="custom"&&S({type:"UPDATE_PERMISSIONS",value:p?.permissions}),p},{enabled:!m&&!l,onError(){T({type:"warning",message:{id:"notification.error",defaultMessage:"An error occured"}})}}),Le=async(p,K)=>{P.current(m?"willCreateToken":"willEditToken",{tokenType:Y.Z}),i();const V=p.lifespan&&parseInt(p.lifespan,10)&&p.lifespan!=="0"?parseInt(p.lifespan,10):null;try{const{data:{data:O}}=m?await Q("/admin/api-tokens",{...p,lifespan:V,permissions:p.type==="custom"?H.selectedActions:null}):await ne(`/admin/api-tokens/${F}`,{name:p.name,description:p.description,type:p.type,permissions:p.type==="custom"?H.selectedActions:null});m&&(d.replace(`/settings/api-tokens/${O.id}`,{apiToken:O}),U("apiTokens.success")),r(),k({...O}),T({type:"success",message:o(m?{id:"notification.success.tokencreated",defaultMessage:"API Token successfully created"}:{id:"notification.success.tokenedited",defaultMessage:"API Token successfully edited"})}),P.current(m?"didCreateToken":"didEditToken",{type:l.type,tokenType:Y.Z})}catch(O){const oe=(0,f.Iz)(O.response.data);K.setErrors(oe),O?.response?.data?.error?.message===Se?T({type:"warning",message:O.response.data.message||"notification.error.tokennamenotunique"}):T({type:"warning",message:O?.response?.data?.message||"notification.error"}),r()}},[xe,se]=(0,e.useState)(!1),Be={...H,onChange:({target:{value:p}})=>{se(!0),S({type:"ON_CHANGE",value:p})},onChangeSelectAll:({target:{value:p}})=>{se(!0),S({type:"SELECT_ALL_IN_PERMISSION",value:p})},setSelectedAction:({target:{value:p}})=>{S({type:"SET_SELECTED_ACTION",value:p})}},ae=x&&!m||$&&m;return!m&&!l&&_e!=="success"?e.createElement(c,{apiTokenName:l?.name}):e.createElement(_,{value:Be},e.createElement(t.Main,null,e.createElement(a.SettingsPageTitle,{name:"API Tokens"}),e.createElement(u.Formik,{validationSchema:C.fK,validateOnChange:!1,initialValues:{name:l?.name||"",description:l?.description||"",type:l?.type,lifespan:l?.lifespan?l.lifespan.toString():l?.lifespan},enableReinitialize:!0,onSubmit:(p,K)=>Le(p,K)},({errors:p,handleChange:K,isSubmitting:V,values:O,setFieldValue:oe})=>(xe&&O?.type!=="custom"&&oe("type","custom"),e.createElement(a.Form,null,e.createElement(Ie.Z,{backUrl:"/settings/api-tokens",title:{id:"Settings.apiTokens.createPage.title",defaultMessage:"Create API Token"},token:l,setToken:k,canEditInputs:ae,canRegenerate:j,isSubmitting:V,regenerateUrl:"/admin/api-tokens/"}),e.createElement(t.ContentLayout,null,e.createElement(t.Stack,{spacing:6},Boolean(l?.name)&&e.createElement(Oe.Z,{token:l?.accessKey,tokenType:Y.Z}),e.createElement(Pe,{errors:p,onChange:K,canEditInputs:ae,isCreating:m,values:O,apiToken:l,onDispatch:S,setHasChangedPermissions:se}),e.createElement(ve,{disabled:!ae||O?.type==="read-only"||O?.type==="full-access"}))))))))}},37944:function(b,M,n){"use strict";n.d(M,{IX:function(){return t},fK:function(){return f},mk:function(){return v}});var e=n(64213),g=n(54049),t=(s,y,c="en")=>{if(y&&typeof y=="number"){const R=y/24/60/60/1e3;return(0,e.format)((0,e.addDays)(new Date(s),R),"PPP",{locale:g[c]})}return"Unlimited"},u=n(5173),A=n(13478),f=u.Ry().shape({name:u.Z_(A.translatedErrors.string).required(A.translatedErrors.required),type:u.Z_(A.translatedErrors.string).oneOf(["read-only","full-access","custom"]).required(A.translatedErrors.required),description:u.Z_().nullable(),lifespan:u.Rx().integer().min(0).nullable().defined(A.translatedErrors.required)}),C=n(92891),v=s=>{const y={allActionsIds:[],permissions:[]};return y.permissions=Object.keys(s).map(c=>({apiId:c,label:c.split("::")[1],controllers:(0,C.flatten)(Object.keys(s[c].controllers).map(R=>({controller:R,actions:(0,C.flatten)(s[c].controllers[R].map(I=>{const _=`${c}.${R}.${I}`;return c.includes("api::")&&y.allActionsIds.push(_),{action:I,actionId:_}}))})))})),y}},15738:function(b,M,n){var e=n(4293);function g(a){var t=a==null?0:a.length;return t?e(a,1,t):[]}b.exports=g}}]);