@strapi/admin 4.4.0-beta.1 → 4.4.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/StrapiApp.js +4 -12
- package/admin/src/components/Providers/index.js +14 -10
- package/admin/src/content-manager/components/FieldTypeIcon/index.js +31 -1
- package/admin/src/content-manager/components/Inputs/index.js +30 -10
- package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +7 -2
- package/admin/src/content-manager/pages/EditSettingsView/index.js +2 -1
- package/admin/src/content-manager/pages/EditView/index.js +91 -84
- package/admin/src/core/apis/CustomFields.js +80 -0
- package/admin/src/core/apis/index.js +1 -0
- package/admin/src/hooks/index.js +0 -1
- package/admin/src/pages/AuthPage/utils/forms.js +2 -2
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +180 -452
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +1 -2
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +0 -1
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +36 -3
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +11 -13
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +2 -3
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
- package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/utils/schema.js +1 -1
- package/admin/src/pages/SettingsPage/pages/Users/utils/validations/users/profile.js +2 -2
- package/admin/src/permissions/defaultPermissions.js +6 -2
- package/admin/src/translations/en.json +0 -17
- package/build/524.40377968.chunk.js +644 -0
- package/build/{Admin-authenticatedApp.3a31a087.chunk.js → Admin-authenticatedApp.50e41ff2.chunk.js} +1 -1
- package/build/{Admin_homePage.6d5e3236.chunk.js → Admin_homePage.118926e0.chunk.js} +1 -1
- package/build/{Admin_profilePage.83991a6c.chunk.js → Admin_profilePage.9d50ac44.chunk.js} +4 -4
- package/build/{Admin_settingsPage.fc9c607a.chunk.js → Admin_settingsPage.98a711e5.chunk.js} +16 -16
- package/build/admin-app.8bc3e80f.chunk.js +112 -0
- package/build/admin-edit-roles-page.554ba3fa.chunk.js +1 -0
- package/build/{admin-edit-users.5bebf473.chunk.js → admin-edit-users.c585212f.chunk.js} +2 -2
- package/build/{admin-users.dccd5f4c.chunk.js → admin-users.97a08630.chunk.js} +1 -1
- package/build/api-tokens-create-page.4c262d6e.chunk.js +1 -0
- package/build/api-tokens-edit-page.10a9d368.chunk.js +1 -0
- package/build/api-tokens-list-page.442c9f3c.chunk.js +15 -0
- package/build/content-manager.2a6f876d.chunk.js +1178 -0
- package/build/content-type-builder-list-view.5b3cd768.chunk.js +194 -0
- package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +1 -0
- package/build/content-type-builder.d4610e20.chunk.js +145 -0
- package/build/en-json.12bc5a14.chunk.js +1 -0
- package/build/index.html +1 -1
- package/build/{main.cdfda31e.js → main.fdc482f3.js} +1151 -1151
- package/build/{runtime~main.fa8f8898.js → runtime~main.29105d25.js} +2 -2
- package/build/sso-settings-page.445184e0.chunk.js +1 -0
- package/build/{webhook-edit-page.9e46fc3f.chunk.js → webhook-edit-page.d2ea3351.chunk.js} +1 -1
- package/package.json +7 -8
- package/server/bootstrap.js +1 -19
- package/server/config/admin-actions.js +0 -20
- package/server/content-types/api-token.js +1 -25
- package/server/content-types/index.js +0 -1
- package/server/controllers/api-token.js +1 -24
- package/server/controllers/index.js +0 -1
- package/server/routes/api-tokens.js +0 -11
- package/server/routes/index.js +0 -2
- package/server/services/api-token.js +29 -310
- package/server/services/constants.js +0 -10
- package/server/services/permission/engine-hooks.js +82 -0
- package/server/services/permission/engine.js +226 -36
- package/server/services/permission.js +1 -4
- package/server/strategies/admin.js +1 -7
- package/server/strategies/api-token.js +11 -71
- package/server/validation/api-tokens.js +2 -12
- package/server/validation/common-validators.js +1 -1
- package/admin/src/contexts/ApiTokenPermissions/index.js +0 -24
- package/admin/src/hooks/useRegenerate/index.js +0 -34
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +0 -56
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +0 -41
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +0 -72
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +0 -30
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +0 -150
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +0 -37
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +0 -40
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +0 -68
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +0 -13
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +0 -55
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +0 -16
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +0 -5
- package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +0 -36
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +0 -63
- package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +0 -19
- package/build/4235.982b5799.chunk.js +0 -30
- package/build/611.a91aff91.chunk.js +0 -158
- package/build/7379.d246dd38.chunk.js +0 -1
- package/build/admin-app.41b6472c.chunk.js +0 -112
- package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +0 -1
- package/build/api-tokens-create-page.29cc87b6.chunk.js +0 -1
- package/build/api-tokens-edit-page.c294a88f.chunk.js +0 -1
- package/build/api-tokens-list-page.bb36535f.chunk.js +0 -16
- package/build/content-manager.fb5ee865.chunk.js +0 -1178
- package/build/content-type-builder-list-view.8cc534e0.chunk.js +0 -194
- package/build/content-type-builder-translation-en-json.201bfb78.chunk.js +0 -1
- package/build/content-type-builder.42cecba9.chunk.js +0 -142
- package/build/en-json.a9918c93.chunk.js +0 -1
- package/build/sso-settings-page.9ceb0140.chunk.js +0 -1
- package/server/content-types/api-token-permission.js +0 -36
- package/server/controllers/content-api.js +0 -15
- package/server/routes/content-api.js +0 -20
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
-
const { isNil } = require('lodash/fp');
|
|
5
|
-
const { omit, difference, isEmpty, map, isArray, uniq } = require('lodash/fp');
|
|
6
|
-
const { ValidationError, NotFoundError } = require('@strapi/utils').errors;
|
|
7
|
-
const constants = require('./constants');
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
|
-
* @typedef {'read-only'|'full-access'
|
|
6
|
+
* @typedef {'read-only'|'full-access'} TokenType
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
/**
|
|
@@ -15,135 +11,20 @@ const constants = require('./constants');
|
|
|
15
11
|
*
|
|
16
12
|
* @property {number|string} id
|
|
17
13
|
* @property {string} name
|
|
18
|
-
* @property {string} description
|
|
14
|
+
* @property {string} [description]
|
|
19
15
|
* @property {string} accessKey
|
|
20
|
-
* @property {number} lastUsedAt
|
|
21
|
-
* @property {number} lifespan
|
|
22
|
-
* @property {number} expiresAt
|
|
23
16
|
* @property {TokenType} type
|
|
24
|
-
* @property {(number|ApiTokenPermission)[]} permissions
|
|
25
17
|
*/
|
|
26
18
|
|
|
27
|
-
/**
|
|
28
|
-
* @typedef ApiTokenPermission
|
|
29
|
-
*
|
|
30
|
-
* @property {number|string} id
|
|
31
|
-
* @property {string} action
|
|
32
|
-
* @property {ApiToken|number} token
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
/** @constant {Array<string>} */
|
|
36
|
-
const SELECT_FIELDS = [
|
|
37
|
-
'id',
|
|
38
|
-
'name',
|
|
39
|
-
'description',
|
|
40
|
-
'lastUsedAt',
|
|
41
|
-
'type',
|
|
42
|
-
'lifespan',
|
|
43
|
-
'expiresAt',
|
|
44
|
-
'createdAt',
|
|
45
|
-
'updatedAt',
|
|
46
|
-
];
|
|
47
|
-
|
|
48
19
|
/** @constant {Array<string>} */
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
// TODO: we need to ensure the permissions are actually valid registered permissions!
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Assert that a token's permissions attribute is valid for its type
|
|
55
|
-
*
|
|
56
|
-
* @param {ApiToken} token
|
|
57
|
-
*/
|
|
58
|
-
const assertCustomTokenPermissionsValidity = (attributes) => {
|
|
59
|
-
// Ensure non-custom tokens doesn't have permissions
|
|
60
|
-
if (attributes.type !== constants.API_TOKEN_TYPE.CUSTOM && !isEmpty(attributes.permissions)) {
|
|
61
|
-
throw new ValidationError('Non-custom tokens should not reference permissions');
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Custom type tokens should always have permissions attached to them
|
|
65
|
-
if (attributes.type === constants.API_TOKEN_TYPE.CUSTOM && !isArray(attributes.permissions)) {
|
|
66
|
-
throw new ValidationError('Missing permissions attribute for custom token');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Permissions provided for a custom type token should be valid/registered permissions UID
|
|
70
|
-
if (attributes.type === constants.API_TOKEN_TYPE.CUSTOM) {
|
|
71
|
-
const validPermissions = strapi.contentAPI.permissions.providers.action.keys();
|
|
72
|
-
const invalidPermissions = difference(attributes.permissions, validPermissions);
|
|
73
|
-
|
|
74
|
-
if (!isEmpty(invalidPermissions)) {
|
|
75
|
-
throw new ValidationError(`Unknown permissions provided: ${invalidPermissions.join(', ')}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Assert that a token's permissions attribute is valid for its type
|
|
82
|
-
*
|
|
83
|
-
* @param {ApiToken} token
|
|
84
|
-
*/
|
|
85
|
-
const assertValidLifespan = ({ lifespan }) => {
|
|
86
|
-
if (isNil(lifespan)) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!Object.values(constants.API_TOKEN_LIFESPANS).includes(lifespan)) {
|
|
91
|
-
throw new ValidationError(
|
|
92
|
-
`lifespan must be one of the following values:
|
|
93
|
-
${Object.values(constants.API_TOKEN_LIFESPANS).join(', ')}`
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Flatten a token's database permissions objects to an array of strings
|
|
100
|
-
*
|
|
101
|
-
* @param {ApiToken} token
|
|
102
|
-
*
|
|
103
|
-
* @returns {ApiToken}
|
|
104
|
-
*/
|
|
105
|
-
const flattenTokenPermissions = (token) => {
|
|
106
|
-
if (!token) return token;
|
|
107
|
-
return {
|
|
108
|
-
...token,
|
|
109
|
-
permissions: isArray(token.permissions) ? map('action', token.permissions) : token.permissions,
|
|
110
|
-
};
|
|
111
|
-
};
|
|
20
|
+
const SELECT_FIELDS = ['id', 'name', 'description', 'type', 'createdAt'];
|
|
112
21
|
|
|
113
22
|
/**
|
|
114
|
-
* Get a token
|
|
115
|
-
*
|
|
116
23
|
* @param {Object} whereParams
|
|
117
|
-
* @param {string|number} whereParams.id
|
|
118
|
-
* @param {string} whereParams.name
|
|
119
|
-
* @param {
|
|
120
|
-
* @param {string} whereParams.
|
|
121
|
-
* @param {string} whereParams.accessKey
|
|
122
|
-
*
|
|
123
|
-
* @returns {Promise<Omit<ApiToken, 'accessKey'> | null>}
|
|
124
|
-
*/
|
|
125
|
-
const getBy = async (whereParams = {}) => {
|
|
126
|
-
if (Object.keys(whereParams).length === 0) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const token = await strapi
|
|
131
|
-
.query('admin::api-token')
|
|
132
|
-
.findOne({ select: SELECT_FIELDS, populate: POPULATE_FIELDS, where: whereParams });
|
|
133
|
-
|
|
134
|
-
if (!token) return token;
|
|
135
|
-
return flattenTokenPermissions(token);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Check if token exists
|
|
140
|
-
*
|
|
141
|
-
* @param {Object} whereParams
|
|
142
|
-
* @param {string|number} whereParams.id
|
|
143
|
-
* @param {string} whereParams.name
|
|
144
|
-
* @param {number} whereParams.lastUsedAt
|
|
145
|
-
* @param {string} whereParams.description
|
|
146
|
-
* @param {string} whereParams.accessKey
|
|
24
|
+
* @param {string|number} [whereParams.id]
|
|
25
|
+
* @param {string} [whereParams.name]
|
|
26
|
+
* @param {string} [whereParams.description]
|
|
27
|
+
* @param {string} [whereParams.accessKey]
|
|
147
28
|
*
|
|
148
29
|
* @returns {Promise<boolean>}
|
|
149
30
|
*/
|
|
@@ -154,8 +35,6 @@ const exists = async (whereParams = {}) => {
|
|
|
154
35
|
};
|
|
155
36
|
|
|
156
37
|
/**
|
|
157
|
-
* Return a secure sha512 hash of an accessKey
|
|
158
|
-
*
|
|
159
38
|
* @param {string} accessKey
|
|
160
39
|
*
|
|
161
40
|
* @returns {string}
|
|
@@ -168,103 +47,24 @@ const hash = (accessKey) => {
|
|
|
168
47
|
};
|
|
169
48
|
|
|
170
49
|
/**
|
|
171
|
-
* @param {number} lifespan
|
|
172
|
-
*
|
|
173
|
-
* @returns { { lifespan: null | number, expiresAt: null | number } }
|
|
174
|
-
*/
|
|
175
|
-
const getExpirationFields = (lifespan) => {
|
|
176
|
-
// it must be nil or a finite number >= 0
|
|
177
|
-
const isValidNumber = Number.isFinite(lifespan) && lifespan > 0;
|
|
178
|
-
if (!isValidNumber && !isNil(lifespan)) {
|
|
179
|
-
throw new ValidationError('lifespan must be a positive number or null');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
lifespan: lifespan || null,
|
|
184
|
-
expiresAt: lifespan ? Date.now() + lifespan : null,
|
|
185
|
-
};
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Create a token and its permissions
|
|
190
|
-
*
|
|
191
50
|
* @param {Object} attributes
|
|
192
51
|
* @param {TokenType} attributes.type
|
|
193
52
|
* @param {string} attributes.name
|
|
194
|
-
* @param {
|
|
195
|
-
* @param {string[]} attributes.permissions
|
|
196
|
-
* @param {string} attributes.description
|
|
53
|
+
* @param {string} [attributes.description]
|
|
197
54
|
*
|
|
198
55
|
* @returns {Promise<ApiToken>}
|
|
199
56
|
*/
|
|
200
57
|
const create = async (attributes) => {
|
|
201
58
|
const accessKey = crypto.randomBytes(128).toString('hex');
|
|
202
59
|
|
|
203
|
-
assertCustomTokenPermissionsValidity(attributes);
|
|
204
|
-
assertValidLifespan(attributes);
|
|
205
|
-
|
|
206
|
-
// Create the token
|
|
207
60
|
const apiToken = await strapi.query('admin::api-token').create({
|
|
208
61
|
select: SELECT_FIELDS,
|
|
209
|
-
populate: POPULATE_FIELDS,
|
|
210
|
-
data: {
|
|
211
|
-
...omit('permissions', attributes),
|
|
212
|
-
accessKey: hash(accessKey),
|
|
213
|
-
...getExpirationFields(attributes.lifespan),
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const result = { ...apiToken, accessKey };
|
|
218
|
-
|
|
219
|
-
// If this is a custom type token, create and the related permissions
|
|
220
|
-
if (attributes.type === constants.API_TOKEN_TYPE.CUSTOM) {
|
|
221
|
-
// TODO: createMany doesn't seem to create relation properly, implement a better way rather than a ton of queries
|
|
222
|
-
// const permissionsCount = await strapi.query('admin::api-token-permission').createMany({
|
|
223
|
-
// populate: POPULATE_FIELDS,
|
|
224
|
-
// data: attributes.permissions.map(action => ({ action, token: apiToken })),
|
|
225
|
-
// });
|
|
226
|
-
await Promise.all(
|
|
227
|
-
uniq(attributes.permissions).map((action) =>
|
|
228
|
-
strapi.query('admin::api-token-permission').create({
|
|
229
|
-
data: { action, token: apiToken },
|
|
230
|
-
})
|
|
231
|
-
)
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const currentPermissions = await strapi.entityService.load(
|
|
235
|
-
'admin::api-token',
|
|
236
|
-
apiToken,
|
|
237
|
-
'permissions'
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
if (currentPermissions) {
|
|
241
|
-
Object.assign(result, { permissions: map('action', currentPermissions) });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return result;
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* @param {string|number} id
|
|
250
|
-
*
|
|
251
|
-
* @returns {Promise<ApiToken>}
|
|
252
|
-
*/
|
|
253
|
-
const regenerate = async (id) => {
|
|
254
|
-
const accessKey = crypto.randomBytes(128).toString('hex');
|
|
255
|
-
|
|
256
|
-
const apiToken = await strapi.query('admin::api-token').update({
|
|
257
|
-
select: ['id', 'accessKey'],
|
|
258
|
-
where: { id },
|
|
259
62
|
data: {
|
|
63
|
+
...attributes,
|
|
260
64
|
accessKey: hash(accessKey),
|
|
261
65
|
},
|
|
262
66
|
});
|
|
263
67
|
|
|
264
|
-
if (!apiToken) {
|
|
265
|
-
throw new NotFoundError('The provided token id does not exist');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
68
|
return {
|
|
269
69
|
...apiToken,
|
|
270
70
|
accessKey,
|
|
@@ -292,37 +92,25 @@ For security reasons, prefer storing the secret in an environment variable and r
|
|
|
292
92
|
};
|
|
293
93
|
|
|
294
94
|
/**
|
|
295
|
-
* Return a list of all tokens and their permissions
|
|
296
|
-
*
|
|
297
95
|
* @returns {Promise<Omit<ApiToken, 'accessKey'>>}
|
|
298
96
|
*/
|
|
299
97
|
const list = async () => {
|
|
300
|
-
|
|
98
|
+
return strapi.query('admin::api-token').findMany({
|
|
301
99
|
select: SELECT_FIELDS,
|
|
302
|
-
populate: POPULATE_FIELDS,
|
|
303
100
|
orderBy: { name: 'ASC' },
|
|
304
101
|
});
|
|
305
|
-
|
|
306
|
-
if (!tokens) return tokens;
|
|
307
|
-
return tokens.map((token) => flattenTokenPermissions(token));
|
|
308
102
|
};
|
|
309
103
|
|
|
310
104
|
/**
|
|
311
|
-
* Revoke (delete) a token
|
|
312
|
-
*
|
|
313
105
|
* @param {string|number} id
|
|
314
106
|
*
|
|
315
107
|
* @returns {Promise<Omit<ApiToken, 'accessKey'>>}
|
|
316
108
|
*/
|
|
317
109
|
const revoke = async (id) => {
|
|
318
|
-
return strapi
|
|
319
|
-
.query('admin::api-token')
|
|
320
|
-
.delete({ select: SELECT_FIELDS, populate: POPULATE_FIELDS, where: { id } });
|
|
110
|
+
return strapi.query('admin::api-token').delete({ select: SELECT_FIELDS, where: { id } });
|
|
321
111
|
};
|
|
322
112
|
|
|
323
113
|
/**
|
|
324
|
-
* Retrieve a token by id
|
|
325
|
-
*
|
|
326
114
|
* @param {string|number} id
|
|
327
115
|
*
|
|
328
116
|
* @returns {Promise<Omit<ApiToken, 'accessKey'>>}
|
|
@@ -332,8 +120,6 @@ const getById = async (id) => {
|
|
|
332
120
|
};
|
|
333
121
|
|
|
334
122
|
/**
|
|
335
|
-
* Retrieve a token by name
|
|
336
|
-
*
|
|
337
123
|
* @param {string} name
|
|
338
124
|
*
|
|
339
125
|
* @returns {Promise<Omit<ApiToken, 'accessKey'>>}
|
|
@@ -343,106 +129,39 @@ const getByName = async (name) => {
|
|
|
343
129
|
};
|
|
344
130
|
|
|
345
131
|
/**
|
|
346
|
-
* Update a token and its permissions
|
|
347
|
-
*
|
|
348
132
|
* @param {string|number} id
|
|
349
133
|
* @param {Object} attributes
|
|
350
134
|
* @param {TokenType} attributes.type
|
|
351
135
|
* @param {string} attributes.name
|
|
352
|
-
* @param {
|
|
353
|
-
* @param {string[]} attributes.permissions
|
|
354
|
-
* @param {string} attributes.description
|
|
136
|
+
* @param {string} [attributes.description]
|
|
355
137
|
*
|
|
356
138
|
* @returns {Promise<Omit<ApiToken, 'accessKey'>>}
|
|
357
139
|
*/
|
|
358
140
|
const update = async (id, attributes) => {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
throw new NotFoundError('Token not found');
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const changingTypeToCustom =
|
|
367
|
-
attributes.type === constants.API_TOKEN_TYPE.CUSTOM &&
|
|
368
|
-
originalToken.type !== constants.API_TOKEN_TYPE.CUSTOM;
|
|
369
|
-
|
|
370
|
-
// if we're updating the permissions on any token type, or changing from non-custom to custom, ensure they're still valid
|
|
371
|
-
// if neither type nor permissions are changing, we don't need to validate again or else we can't allow partial update
|
|
372
|
-
if (attributes.permissions || changingTypeToCustom) {
|
|
373
|
-
assertCustomTokenPermissionsValidity({
|
|
374
|
-
...originalToken,
|
|
375
|
-
...attributes,
|
|
376
|
-
type: attributes.type || originalToken.type,
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
assertValidLifespan(attributes);
|
|
381
|
-
|
|
382
|
-
const updatedToken = await strapi.query('admin::api-token').update({
|
|
383
|
-
select: SELECT_FIELDS,
|
|
384
|
-
populate: POPULATE_FIELDS,
|
|
385
|
-
where: { id },
|
|
386
|
-
data: omit('permissions', attributes),
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// custom tokens need to have their permissions updated as well
|
|
390
|
-
if (updatedToken.type === constants.API_TOKEN_TYPE.CUSTOM && attributes.permissions) {
|
|
391
|
-
const currentPermissionsResult = await strapi.entityService.load(
|
|
392
|
-
'admin::api-token',
|
|
393
|
-
updatedToken,
|
|
394
|
-
'permissions'
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
const currentPermissions = map('action', currentPermissionsResult || []);
|
|
398
|
-
const newPermissions = uniq(attributes.permissions);
|
|
399
|
-
|
|
400
|
-
const actionsToDelete = difference(currentPermissions, newPermissions);
|
|
401
|
-
const actionsToAdd = difference(newPermissions, currentPermissions);
|
|
402
|
-
|
|
403
|
-
// TODO: improve efficiency here
|
|
404
|
-
// method using a loop -- works but very inefficient
|
|
405
|
-
await Promise.all(
|
|
406
|
-
actionsToDelete.map((action) =>
|
|
407
|
-
strapi.query('admin::api-token-permission').delete({
|
|
408
|
-
where: { action, token: id },
|
|
409
|
-
})
|
|
410
|
-
)
|
|
411
|
-
);
|
|
141
|
+
return strapi
|
|
142
|
+
.query('admin::api-token')
|
|
143
|
+
.update({ where: { id }, data: attributes, select: SELECT_FIELDS });
|
|
144
|
+
};
|
|
412
145
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
await strapi.query('admin::api-token-permission').delete({
|
|
426
|
-
where: { token: id },
|
|
427
|
-
});
|
|
146
|
+
/**
|
|
147
|
+
* @param {Object} whereParams
|
|
148
|
+
* @param {string|number} [whereParams.id]
|
|
149
|
+
* @param {string} [whereParams.name]
|
|
150
|
+
* @param {string} [whereParams.description]
|
|
151
|
+
* @param {string} [whereParams.accessKey]
|
|
152
|
+
*
|
|
153
|
+
* @returns {Promise<Omit<ApiToken, 'accessKey'> | null>}
|
|
154
|
+
*/
|
|
155
|
+
const getBy = async (whereParams = {}) => {
|
|
156
|
+
if (Object.keys(whereParams).length === 0) {
|
|
157
|
+
return null;
|
|
428
158
|
}
|
|
429
159
|
|
|
430
|
-
|
|
431
|
-
const permissionsFromDb = await strapi.entityService.load(
|
|
432
|
-
'admin::api-token',
|
|
433
|
-
updatedToken,
|
|
434
|
-
'permissions'
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
return {
|
|
438
|
-
...updatedToken,
|
|
439
|
-
permissions: permissionsFromDb ? permissionsFromDb.map((p) => p.action) : undefined,
|
|
440
|
-
};
|
|
160
|
+
return strapi.query('admin::api-token').findOne({ select: SELECT_FIELDS, where: whereParams });
|
|
441
161
|
};
|
|
442
162
|
|
|
443
163
|
module.exports = {
|
|
444
164
|
create,
|
|
445
|
-
regenerate,
|
|
446
165
|
exists,
|
|
447
166
|
checkSaltIsDefined,
|
|
448
167
|
hash,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const DAY_IN_MS = 24 * 60 * 60 * 1000;
|
|
4
|
-
|
|
5
3
|
module.exports = {
|
|
6
4
|
CONTENT_TYPE_SECTION: 'contentTypes',
|
|
7
5
|
SUPER_ADMIN_CODE: 'strapi-super-admin',
|
|
@@ -15,13 +13,5 @@ module.exports = {
|
|
|
15
13
|
API_TOKEN_TYPE: {
|
|
16
14
|
READ_ONLY: 'read-only',
|
|
17
15
|
FULL_ACCESS: 'full-access',
|
|
18
|
-
CUSTOM: 'custom',
|
|
19
|
-
},
|
|
20
|
-
// The front-end only displays these values
|
|
21
|
-
API_TOKEN_LIFESPANS: {
|
|
22
|
-
UNLIMITED: null,
|
|
23
|
-
DAYS_7: 7 * DAY_IN_MS,
|
|
24
|
-
DAYS_30: 30 * DAY_IN_MS,
|
|
25
|
-
DAYS_90: 90 * DAY_IN_MS,
|
|
26
16
|
},
|
|
27
17
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { cloneDeep, has } = require('lodash/fp');
|
|
4
|
+
const { hooks } = require('@strapi/utils');
|
|
5
|
+
|
|
6
|
+
const permissionDomain = require('../../domain/permission');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a hook map used by the permission Engine
|
|
10
|
+
*/
|
|
11
|
+
const createEngineHooks = () => ({
|
|
12
|
+
willEvaluatePermission: hooks.createAsyncSeriesHook(),
|
|
13
|
+
willRegisterPermission: hooks.createAsyncSeriesHook(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a context from a domain {@link Permission} used by the WillEvaluate hook
|
|
18
|
+
* @param {Permission} permission
|
|
19
|
+
* @return {{readonly permission: Permission, addCondition(string): this}}
|
|
20
|
+
*/
|
|
21
|
+
const createWillEvaluateContext = (permission) => ({
|
|
22
|
+
get permission() {
|
|
23
|
+
return cloneDeep(permission);
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
addCondition(condition) {
|
|
27
|
+
Object.assign(permission, permissionDomain.addCondition(condition, permission));
|
|
28
|
+
|
|
29
|
+
return this;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a context from a casl Permission & some options
|
|
35
|
+
* @param caslPermission
|
|
36
|
+
* @param {object} options
|
|
37
|
+
* @param {Permission} options.permission
|
|
38
|
+
* @param {object} options.user
|
|
39
|
+
*/
|
|
40
|
+
const createWillRegisterContext = (caslPermission, { permission, user }) => ({
|
|
41
|
+
get permission() {
|
|
42
|
+
return cloneDeep(permission);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
get user() {
|
|
46
|
+
return cloneDeep(user);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
condition: {
|
|
50
|
+
and(rawConditionObject) {
|
|
51
|
+
if (!caslPermission.condition) {
|
|
52
|
+
Object.assign(caslPermission, { condition: { $and: [] } });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
caslPermission.condition.$and.push(rawConditionObject);
|
|
56
|
+
|
|
57
|
+
return this;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
or(rawConditionObject) {
|
|
61
|
+
if (!caslPermission.condition) {
|
|
62
|
+
Object.assign(caslPermission, { condition: { $and: [] } });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const orClause = caslPermission.condition.$and.find(has('$or'));
|
|
66
|
+
|
|
67
|
+
if (orClause) {
|
|
68
|
+
orClause.$or.push(rawConditionObject);
|
|
69
|
+
} else {
|
|
70
|
+
caslPermission.condition.$and.push({ $or: [rawConditionObject] });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return this;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
createEngineHooks,
|
|
80
|
+
createWillEvaluateContext,
|
|
81
|
+
createWillRegisterContext,
|
|
82
|
+
};
|