@strapi/admin 5.43.0 → 5.45.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.
- package/dist/admin/admin/src/StrapiApp.js +17 -4
- package/dist/admin/admin/src/StrapiApp.js.map +1 -1
- package/dist/admin/admin/src/StrapiApp.mjs +18 -5
- package/dist/admin/admin/src/StrapiApp.mjs.map +1 -1
- package/dist/admin/admin/src/components/Form.js +18 -8
- package/dist/admin/admin/src/components/Form.js.map +1 -1
- package/dist/admin/admin/src/components/Form.mjs +18 -8
- package/dist/admin/admin/src/components/Form.mjs.map +1 -1
- package/dist/admin/admin/src/components/Layouts/Layout.js +1 -0
- package/dist/admin/admin/src/components/Layouts/Layout.js.map +1 -1
- package/dist/admin/admin/src/components/Layouts/Layout.mjs +1 -0
- package/dist/admin/admin/src/components/Layouts/Layout.mjs.map +1 -1
- package/dist/admin/admin/src/components/Table.js.map +1 -1
- package/dist/admin/admin/src/components/Table.mjs.map +1 -1
- package/dist/admin/admin/src/components/Widgets.js +52 -0
- package/dist/admin/admin/src/components/Widgets.js.map +1 -1
- package/dist/admin/admin/src/components/Widgets.mjs +54 -3
- package/dist/admin/admin/src/components/Widgets.mjs.map +1 -1
- package/dist/admin/admin/src/constants.js +49 -0
- package/dist/admin/admin/src/constants.js.map +1 -1
- package/dist/admin/admin/src/constants.mjs +49 -0
- package/dist/admin/admin/src/constants.mjs.map +1 -1
- package/dist/admin/admin/src/core/apis/router.js +4 -4
- package/dist/admin/admin/src/core/apis/router.js.map +1 -1
- package/dist/admin/admin/src/core/apis/router.mjs +4 -4
- package/dist/admin/admin/src/core/apis/router.mjs.map +1 -1
- package/dist/admin/admin/src/features/Tracking.js.map +1 -1
- package/dist/admin/admin/src/features/Tracking.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/{pages/ApiTokens/EditView/components → components/Tokens}/FormApiTokenContainer.js +48 -11
- package/dist/admin/admin/src/pages/Settings/components/Tokens/FormApiTokenContainer.js.map +1 -0
- package/dist/admin/admin/src/pages/Settings/{pages/ApiTokens/EditView/components → components/Tokens}/FormApiTokenContainer.mjs +49 -12
- package/dist/admin/admin/src/pages/Settings/components/Tokens/FormApiTokenContainer.mjs.map +1 -0
- package/dist/admin/admin/src/pages/Settings/components/Tokens/FormHead.js +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/FormHead.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/FormHead.mjs +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/FormHead.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/LifeSpanInput.js +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/LifeSpanInput.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/LifeSpanInput.mjs +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/LifeSpanInput.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/Table.js +21 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/Table.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/Table.mjs +21 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/Table.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/TokenBox.js +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/TokenBox.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/TokenBox.mjs +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/TokenBox.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/constants.js +33 -0
- package/dist/admin/admin/src/pages/Settings/components/Tokens/constants.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/constants.mjs +14 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/constants.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/{pages/ApiTokens/EditView → components/Tokens}/utils/getDateOfExpiration.js +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/utils/getDateOfExpiration.js.map +1 -0
- package/dist/admin/admin/src/pages/Settings/{pages/ApiTokens/EditView → components/Tokens}/utils/getDateOfExpiration.mjs +1 -1
- package/dist/admin/admin/src/pages/Settings/components/Tokens/utils/getDateOfExpiration.mjs.map +1 -0
- package/dist/admin/admin/src/pages/Settings/constants.js +182 -151
- package/dist/admin/admin/src/pages/Settings/constants.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/constants.mjs +182 -151
- package/dist/admin/admin/src/pages/Settings/constants.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/CreateView.js +17 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/CreateView.js.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/CreateView.mjs +15 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/CreateView.mjs.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/EditViewPage.js +314 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/EditViewPage.js.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/EditViewPage.mjs +292 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/EditViewPage.mjs.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/components/AdminPermissions.js +70 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/components/AdminPermissions.js.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/components/AdminPermissions.mjs +49 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/EditView/components/AdminPermissions.mjs.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/ListView.js +254 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/ListView.js.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/ListView.mjs +231 -0
- package/dist/admin/admin/src/pages/Settings/pages/AdminTokens/ListView.mjs.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/EditViewPage.js +42 -33
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/EditViewPage.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/EditViewPage.mjs +43 -34
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/EditViewPage.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/ListView.js +3 -2
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/ListView.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/ListView.mjs +3 -2
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/ListView.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/CollapsePropertyMatrix.js +23 -12
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/CollapsePropertyMatrix.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/CollapsePropertyMatrix.mjs +23 -12
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/CollapsePropertyMatrix.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ConditionsModal.js +124 -35
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ConditionsModal.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ConditionsModal.mjs +126 -37
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ConditionsModal.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ContentTypeCollapses.js +24 -9
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ContentTypeCollapses.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ContentTypeCollapses.mjs +24 -9
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/ContentTypeCollapses.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/GlobalActions.js +5 -3
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/GlobalActions.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/GlobalActions.mjs +5 -3
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/GlobalActions.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/Permissions.js +171 -36
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/Permissions.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/Permissions.mjs +172 -37
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/Permissions.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/PluginsAndSettings.js +5 -3
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/PluginsAndSettings.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/PluginsAndSettings.mjs +5 -3
- package/dist/admin/admin/src/pages/Settings/pages/Roles/components/PluginsAndSettings.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/hooks/usePermissionsDataManager.js +59 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/hooks/usePermissionsDataManager.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/hooks/usePermissionsDataManager.mjs +40 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/hooks/usePermissionsDataManager.mjs.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/createPermissionChecker.js +89 -0
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/createPermissionChecker.js.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/createPermissionChecker.mjs +86 -0
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/createPermissionChecker.mjs.map +1 -0
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/updateValues.js +35 -9
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/updateValues.js.map +1 -1
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/updateValues.mjs +35 -10
- package/dist/admin/admin/src/pages/Settings/pages/Roles/utils/updateValues.mjs.map +1 -1
- package/dist/admin/admin/src/render.js +6 -3
- package/dist/admin/admin/src/render.js.map +1 -1
- package/dist/admin/admin/src/render.mjs +6 -3
- package/dist/admin/admin/src/render.mjs.map +1 -1
- package/dist/admin/admin/src/router.js +4 -4
- package/dist/admin/admin/src/router.js.map +1 -1
- package/dist/admin/admin/src/router.mjs +1 -1
- package/dist/admin/admin/src/router.mjs.map +1 -1
- package/dist/admin/admin/src/services/apiTokens.js +85 -2
- package/dist/admin/admin/src/services/apiTokens.js.map +1 -1
- package/dist/admin/admin/src/services/apiTokens.mjs +80 -3
- package/dist/admin/admin/src/services/apiTokens.mjs.map +1 -1
- package/dist/admin/admin/src/translations/ar.json.js +4 -1
- package/dist/admin/admin/src/translations/ar.json.js.map +1 -1
- package/dist/admin/admin/src/translations/ar.json.mjs +4 -1
- package/dist/admin/admin/src/translations/ar.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/cs.json.js +736 -13
- package/dist/admin/admin/src/translations/cs.json.js.map +1 -1
- package/dist/admin/admin/src/translations/cs.json.mjs +728 -14
- package/dist/admin/admin/src/translations/cs.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/de.json.js +4 -1
- package/dist/admin/admin/src/translations/de.json.js.map +1 -1
- package/dist/admin/admin/src/translations/de.json.mjs +4 -1
- package/dist/admin/admin/src/translations/de.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/en.json.js +20 -2
- package/dist/admin/admin/src/translations/en.json.js.map +1 -1
- package/dist/admin/admin/src/translations/en.json.mjs +20 -2
- package/dist/admin/admin/src/translations/en.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/es.json.js +4 -1
- package/dist/admin/admin/src/translations/es.json.js.map +1 -1
- package/dist/admin/admin/src/translations/es.json.mjs +4 -1
- package/dist/admin/admin/src/translations/es.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/fr.json.js +4 -1
- package/dist/admin/admin/src/translations/fr.json.js.map +1 -1
- package/dist/admin/admin/src/translations/fr.json.mjs +4 -1
- package/dist/admin/admin/src/translations/fr.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/it.json.js +4 -1
- package/dist/admin/admin/src/translations/it.json.js.map +1 -1
- package/dist/admin/admin/src/translations/it.json.mjs +4 -1
- package/dist/admin/admin/src/translations/it.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/ru.json.js +32 -19
- package/dist/admin/admin/src/translations/ru.json.js.map +1 -1
- package/dist/admin/admin/src/translations/ru.json.mjs +32 -19
- package/dist/admin/admin/src/translations/ru.json.mjs.map +1 -1
- package/dist/admin/admin/src/translations/zh-Hans.json.js +4 -1
- package/dist/admin/admin/src/translations/zh-Hans.json.js.map +1 -1
- package/dist/admin/admin/src/translations/zh-Hans.json.mjs +4 -1
- package/dist/admin/admin/src/translations/zh-Hans.json.mjs.map +1 -1
- package/dist/admin/admin/src/utils/getFetchClient.js +33 -4
- package/dist/admin/admin/src/utils/getFetchClient.js.map +1 -1
- package/dist/admin/admin/src/utils/getFetchClient.mjs +33 -4
- package/dist/admin/admin/src/utils/getFetchClient.mjs.map +1 -1
- package/dist/admin/admin/tests/server.js +99 -21
- package/dist/admin/admin/tests/server.js.map +1 -1
- package/dist/admin/admin/tests/server.mjs +99 -21
- package/dist/admin/admin/tests/server.mjs.map +1 -1
- package/dist/admin/src/components/Widgets.d.ts +2 -1
- package/dist/admin/src/constants.d.ts +26 -0
- package/dist/admin/src/core/apis/router.d.ts +1 -1
- package/dist/admin/src/features/Tracking.d.ts +2 -1
- package/dist/admin/src/pages/Settings/components/Tokens/FormApiTokenContainer.d.ts +24 -0
- package/dist/admin/src/pages/Settings/components/Tokens/Table.d.ts +2 -1
- package/dist/admin/src/pages/Settings/components/Tokens/constants.d.ts +17 -0
- package/dist/admin/src/pages/Settings/constants.d.ts +1 -1
- package/dist/admin/src/pages/Settings/pages/AdminTokens/CreateView.d.ts +1 -0
- package/dist/admin/src/pages/Settings/pages/AdminTokens/EditView/EditViewPage.d.ts +2 -0
- package/dist/admin/src/pages/Settings/pages/AdminTokens/EditView/components/AdminPermissions.d.ts +13 -0
- package/dist/admin/src/pages/Settings/pages/AdminTokens/ListView.d.ts +2 -0
- package/dist/admin/src/pages/Settings/pages/Roles/components/CollapsePropertyMatrix.d.ts +4 -3
- package/dist/admin/src/pages/Settings/pages/Roles/components/ConditionsModal.d.ts +3 -1
- package/dist/admin/src/pages/Settings/pages/Roles/components/ContentTypeCollapses.d.ts +1 -0
- package/dist/admin/src/pages/Settings/pages/Roles/components/Permissions.d.ts +5 -0
- package/dist/admin/src/pages/Settings/pages/Roles/hooks/usePermissionsDataManager.d.ts +8 -7
- package/dist/admin/src/pages/Settings/pages/Roles/utils/createPermissionChecker.d.ts +27 -0
- package/dist/admin/src/pages/Settings/pages/Roles/utils/updateValues.d.ts +8 -2
- package/dist/admin/src/services/apiTokens.d.ts +5 -2
- package/dist/admin/src/types/permissions.d.ts +1 -1
- package/dist/admin/src/utils/getFetchClient.d.ts +14 -1
- package/dist/server/server/src/bootstrap.js +37 -5
- package/dist/server/server/src/bootstrap.js.map +1 -1
- package/dist/server/server/src/bootstrap.mjs +37 -5
- package/dist/server/server/src/bootstrap.mjs.map +1 -1
- package/dist/server/server/src/config/admin-actions.js +48 -0
- package/dist/server/server/src/config/admin-actions.js.map +1 -1
- package/dist/server/server/src/config/admin-actions.mjs +48 -0
- package/dist/server/server/src/config/admin-actions.mjs.map +1 -1
- package/dist/server/server/src/content-types/Permission.js +10 -1
- package/dist/server/server/src/content-types/Permission.js.map +1 -1
- package/dist/server/server/src/content-types/Permission.mjs +10 -1
- package/dist/server/server/src/content-types/Permission.mjs.map +1 -1
- package/dist/server/server/src/content-types/User.js +8 -0
- package/dist/server/server/src/content-types/User.js.map +1 -1
- package/dist/server/server/src/content-types/User.mjs +8 -0
- package/dist/server/server/src/content-types/User.mjs.map +1 -1
- package/dist/server/server/src/content-types/api-token.js +27 -1
- package/dist/server/server/src/content-types/api-token.js.map +1 -1
- package/dist/server/server/src/content-types/api-token.mjs +27 -1
- package/dist/server/server/src/content-types/api-token.mjs.map +1 -1
- package/dist/server/server/src/controllers/admin-token.js +194 -0
- package/dist/server/server/src/controllers/admin-token.js.map +1 -0
- package/dist/server/server/src/controllers/admin-token.mjs +192 -0
- package/dist/server/server/src/controllers/admin-token.mjs.map +1 -0
- package/dist/server/server/src/controllers/api-token.js +48 -47
- package/dist/server/server/src/controllers/api-token.js.map +1 -1
- package/dist/server/server/src/controllers/api-token.mjs +48 -47
- package/dist/server/server/src/controllers/api-token.mjs.map +1 -1
- package/dist/server/server/src/controllers/index.js +2 -0
- package/dist/server/server/src/controllers/index.js.map +1 -1
- package/dist/server/server/src/controllers/index.mjs +2 -0
- package/dist/server/server/src/controllers/index.mjs.map +1 -1
- package/dist/server/server/src/domain/permission/index.js +2 -1
- package/dist/server/server/src/domain/permission/index.js.map +1 -1
- package/dist/server/server/src/domain/permission/index.mjs +2 -1
- package/dist/server/server/src/domain/permission/index.mjs.map +1 -1
- package/dist/server/server/src/policies/index.js +2 -0
- package/dist/server/server/src/policies/index.js.map +1 -1
- package/dist/server/server/src/policies/index.mjs +2 -0
- package/dist/server/server/src/policies/index.mjs.map +1 -1
- package/dist/server/server/src/policies/isAdminTokensEnabled.js +16 -0
- package/dist/server/server/src/policies/isAdminTokensEnabled.js.map +1 -0
- package/dist/server/server/src/policies/isAdminTokensEnabled.mjs +14 -0
- package/dist/server/server/src/policies/isAdminTokensEnabled.mjs.map +1 -0
- package/dist/server/server/src/register.js +4 -2
- package/dist/server/server/src/register.js.map +1 -1
- package/dist/server/server/src/register.mjs +4 -2
- package/dist/server/server/src/register.mjs.map +1 -1
- package/dist/server/server/src/routes/admin-tokens.js +140 -0
- package/dist/server/server/src/routes/admin-tokens.js.map +1 -0
- package/dist/server/server/src/routes/admin-tokens.mjs +138 -0
- package/dist/server/server/src/routes/admin-tokens.mjs.map +1 -0
- package/dist/server/server/src/routes/index.js +2 -0
- package/dist/server/server/src/routes/index.js.map +1 -1
- package/dist/server/server/src/routes/index.mjs +2 -0
- package/dist/server/server/src/routes/index.mjs.map +1 -1
- package/dist/server/server/src/services/api-token.js +805 -101
- package/dist/server/server/src/services/api-token.js.map +1 -1
- package/dist/server/server/src/services/api-token.mjs +800 -101
- package/dist/server/server/src/services/api-token.mjs.map +1 -1
- package/dist/server/server/src/services/constants.js +2 -0
- package/dist/server/server/src/services/constants.js.map +1 -1
- package/dist/server/server/src/services/constants.mjs +2 -0
- package/dist/server/server/src/services/constants.mjs.map +1 -1
- package/dist/server/server/src/services/homepage.js +1 -1
- package/dist/server/server/src/services/homepage.js.map +1 -1
- package/dist/server/server/src/services/homepage.mjs +1 -1
- package/dist/server/server/src/services/homepage.mjs.map +1 -1
- package/dist/server/server/src/services/index.js +2 -1
- package/dist/server/server/src/services/index.js.map +1 -1
- package/dist/server/server/src/services/index.mjs +3 -2
- package/dist/server/server/src/services/index.mjs.map +1 -1
- package/dist/server/server/src/services/permission/engine.js +6 -0
- package/dist/server/server/src/services/permission/engine.js.map +1 -1
- package/dist/server/server/src/services/permission/engine.mjs +6 -0
- package/dist/server/server/src/services/permission/engine.mjs.map +1 -1
- package/dist/server/server/src/services/permission/queries.js +11 -2
- package/dist/server/server/src/services/permission/queries.js.map +1 -1
- package/dist/server/server/src/services/permission/queries.mjs +12 -3
- package/dist/server/server/src/services/permission/queries.mjs.map +1 -1
- package/dist/server/server/src/services/role.js +3 -0
- package/dist/server/server/src/services/role.js.map +1 -1
- package/dist/server/server/src/services/role.mjs +3 -0
- package/dist/server/server/src/services/role.mjs.map +1 -1
- package/dist/server/server/src/strategies/admin-token.js +110 -0
- package/dist/server/server/src/strategies/admin-token.js.map +1 -0
- package/dist/server/server/src/strategies/admin-token.mjs +104 -0
- package/dist/server/server/src/strategies/admin-token.mjs.map +1 -0
- package/dist/server/server/src/strategies/api-token-utils.js +56 -0
- package/dist/server/server/src/strategies/api-token-utils.js.map +1 -0
- package/dist/server/server/src/strategies/api-token-utils.mjs +52 -0
- package/dist/server/server/src/strategies/api-token-utils.mjs.map +1 -0
- package/dist/server/server/src/strategies/content-api-token.js +104 -0
- package/dist/server/server/src/strategies/content-api-token.js.map +1 -0
- package/dist/server/server/src/strategies/content-api-token.mjs +98 -0
- package/dist/server/server/src/strategies/content-api-token.mjs.map +1 -0
- package/dist/server/server/src/validation/admin-tokens.js +28 -0
- package/dist/server/server/src/validation/admin-tokens.js.map +1 -0
- package/dist/server/server/src/validation/admin-tokens.mjs +25 -0
- package/dist/server/server/src/validation/admin-tokens.mjs.map +1 -0
- package/dist/server/server/src/validation/api-tokens.js +5 -2
- package/dist/server/server/src/validation/api-tokens.js.map +1 -1
- package/dist/server/server/src/validation/api-tokens.mjs +5 -2
- package/dist/server/server/src/validation/api-tokens.mjs.map +1 -1
- package/dist/server/server/src/validation/project-settings.js +15 -16
- package/dist/server/server/src/validation/project-settings.js.map +1 -1
- package/dist/server/server/src/validation/project-settings.mjs +4 -5
- package/dist/server/server/src/validation/project-settings.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/config/admin-actions.d.ts.map +1 -1
- package/dist/server/src/content-types/Permission.d.ts +9 -0
- package/dist/server/src/content-types/Permission.d.ts.map +1 -1
- package/dist/server/src/content-types/User.d.ts +8 -0
- package/dist/server/src/content-types/User.d.ts.map +1 -1
- package/dist/server/src/content-types/api-token.d.ts +23 -0
- package/dist/server/src/content-types/api-token.d.ts.map +1 -1
- package/dist/server/src/content-types/index.d.ts +40 -0
- package/dist/server/src/content-types/index.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-token.d.ts +12 -0
- package/dist/server/src/controllers/admin-token.d.ts.map +1 -0
- package/dist/server/src/controllers/api-token.d.ts +0 -1
- package/dist/server/src/controllers/api-token.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +9 -1
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/domain/permission/index.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +56 -2
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/policies/index.d.ts +5 -0
- package/dist/server/src/policies/index.d.ts.map +1 -1
- package/dist/server/src/policies/isAdminTokensEnabled.d.ts +7 -0
- package/dist/server/src/policies/isAdminTokensEnabled.d.ts.map +1 -0
- package/dist/server/src/register.d.ts.map +1 -1
- package/dist/server/src/routes/admin-tokens.d.ts +15 -0
- package/dist/server/src/routes/admin-tokens.d.ts.map +1 -0
- package/dist/server/src/routes/index.d.ts.map +1 -1
- package/dist/server/src/services/api-token.d.ts +136 -12
- package/dist/server/src/services/api-token.d.ts.map +1 -1
- package/dist/server/src/services/constants.d.ts +13 -11
- package/dist/server/src/services/constants.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +2 -2
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/permission/engine.d.ts +5 -0
- package/dist/server/src/services/permission/engine.d.ts.map +1 -1
- package/dist/server/src/services/permission/queries.d.ts.map +1 -1
- package/dist/server/src/services/permission.d.ts +1 -0
- package/dist/server/src/services/permission.d.ts.map +1 -1
- package/dist/server/src/services/role.d.ts.map +1 -1
- package/dist/server/src/strategies/admin-token.d.ts +51 -0
- package/dist/server/src/strategies/admin-token.d.ts.map +1 -0
- package/dist/server/src/strategies/api-token-utils.d.ts +13 -0
- package/dist/server/src/strategies/api-token-utils.d.ts.map +1 -0
- package/dist/server/src/strategies/{api-token.d.ts → content-api-token.d.ts} +10 -11
- package/dist/server/src/strategies/content-api-token.d.ts.map +1 -0
- package/dist/server/src/strategies/index.d.ts +2 -1
- package/dist/server/src/strategies/index.d.ts.map +1 -1
- package/dist/server/src/validation/admin-tokens.d.ts +75 -0
- package/dist/server/src/validation/admin-tokens.d.ts.map +1 -0
- package/dist/server/src/validation/api-tokens.d.ts +4 -2
- package/dist/server/src/validation/api-tokens.d.ts.map +1 -1
- package/dist/server/src/validation/project-settings.d.ts +10 -10
- package/dist/server/src/validation/project-settings.d.ts.map +1 -1
- package/dist/shared/contracts/admin-token.d.ts +122 -0
- package/dist/shared/contracts/admin-token.d.ts.map +1 -0
- package/dist/shared/contracts/api-token.d.ts +6 -95
- package/dist/shared/contracts/api-token.d.ts.map +1 -1
- package/dist/shared/contracts/content-api-token.d.ts +97 -0
- package/dist/shared/contracts/content-api-token.d.ts.map +1 -0
- package/dist/shared/contracts/shared.d.ts +1 -0
- package/dist/shared/contracts/shared.d.ts.map +1 -1
- package/package.json +10 -10
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/components/FormApiTokenContainer.js.map +0 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/components/FormApiTokenContainer.mjs.map +0 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/constants.js +0 -37
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/constants.js.map +0 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/constants.mjs +0 -16
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/constants.mjs.map +0 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/utils/getDateOfExpiration.js.map +0 -1
- package/dist/admin/admin/src/pages/Settings/pages/ApiTokens/EditView/utils/getDateOfExpiration.mjs.map +0 -1
- package/dist/admin/src/pages/Settings/pages/ApiTokens/EditView/components/FormApiTokenContainer.d.ts +0 -20
- package/dist/admin/src/pages/Settings/pages/ApiTokens/EditView/constants.d.ts +0 -17
- package/dist/server/server/src/strategies/api-token.js +0 -144
- package/dist/server/server/src/strategies/api-token.js.map +0 -1
- package/dist/server/server/src/strategies/api-token.mjs +0 -138
- package/dist/server/server/src/strategies/api-token.mjs.map +0 -1
- package/dist/server/src/strategies/api-token.d.ts.map +0 -1
- /package/dist/admin/src/pages/Settings/{pages/ApiTokens/EditView → components/Tokens}/utils/getDateOfExpiration.d.ts +0 -0
|
@@ -5,10 +5,56 @@ var fp = require('lodash/fp');
|
|
|
5
5
|
var utils = require('@strapi/utils');
|
|
6
6
|
var constants = require('./constants.js');
|
|
7
7
|
var index = require('../utils/index.js');
|
|
8
|
+
var index$1 = require('../domain/permission/index.js');
|
|
9
|
+
var permission = require('../validation/permission.js');
|
|
8
10
|
|
|
11
|
+
const { SUPER_ADMIN_CODE } = constants;
|
|
9
12
|
const { ValidationError, NotFoundError } = utils.errors;
|
|
13
|
+
const assertOwnerMatchesCallingUser = async (adminUserOwner, callingUser)=>{
|
|
14
|
+
if (callingUser === undefined || callingUser === null) {
|
|
15
|
+
throw new ValidationError('adminUserOwner requires an authenticated admin user');
|
|
16
|
+
}
|
|
17
|
+
const ownerId = String(adminUserOwner);
|
|
18
|
+
const callingUserId = String(callingUser.id);
|
|
19
|
+
if (ownerId !== callingUserId) {
|
|
20
|
+
throw new ValidationError('adminUserOwner must match the authenticated admin user');
|
|
21
|
+
}
|
|
22
|
+
const existingUser = await strapi.db.query('admin::user').findOne({
|
|
23
|
+
select: [
|
|
24
|
+
'id'
|
|
25
|
+
],
|
|
26
|
+
where: {
|
|
27
|
+
id: callingUser.id
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
if (existingUser === null || existingUser === undefined) {
|
|
31
|
+
throw new ValidationError('adminUserOwner must reference an existing admin user');
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const isSuperAdmin = (user)=>user?.roles?.some((r)=>r.code === SUPER_ADMIN_CODE) === true;
|
|
35
|
+
const getOwnerId = (token)=>{
|
|
36
|
+
const owner = token.adminUserOwner;
|
|
37
|
+
return String(typeof owner === 'object' ? owner.id : owner);
|
|
38
|
+
};
|
|
39
|
+
const toAdminTokenOwner = (owner)=>{
|
|
40
|
+
if (owner === null || owner === undefined) {
|
|
41
|
+
throw new Error('adminUserOwner is required');
|
|
42
|
+
}
|
|
43
|
+
// Bare id
|
|
44
|
+
if (typeof owner === 'number' || typeof owner === 'string') {
|
|
45
|
+
return owner;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
id: owner.id,
|
|
49
|
+
firstname: owner.firstname,
|
|
50
|
+
lastname: owner.lastname,
|
|
51
|
+
username: owner.username,
|
|
52
|
+
email: owner.email
|
|
53
|
+
};
|
|
54
|
+
};
|
|
10
55
|
const SELECT_FIELDS = [
|
|
11
56
|
'id',
|
|
57
|
+
'kind',
|
|
12
58
|
'name',
|
|
13
59
|
'description',
|
|
14
60
|
'lastUsedAt',
|
|
@@ -19,7 +65,9 @@ const SELECT_FIELDS = [
|
|
|
19
65
|
'updatedAt'
|
|
20
66
|
];
|
|
21
67
|
const POPULATE_FIELDS = [
|
|
22
|
-
'permissions'
|
|
68
|
+
'permissions',
|
|
69
|
+
'adminPermissions',
|
|
70
|
+
'adminUserOwner'
|
|
23
71
|
];
|
|
24
72
|
// TODO: we need to ensure the permissions are actually valid registered permissions!
|
|
25
73
|
/**
|
|
@@ -62,42 +110,381 @@ const POPULATE_FIELDS = [
|
|
|
62
110
|
}
|
|
63
111
|
};
|
|
64
112
|
/**
|
|
65
|
-
*
|
|
66
|
-
*/ const
|
|
67
|
-
|
|
68
|
-
|
|
113
|
+
* Assert that a legacy-kind token body does not carry admin-only fields
|
|
114
|
+
*/ const assertLegacyKindFields = (attributes)=>{
|
|
115
|
+
const raw = attributes;
|
|
116
|
+
if (raw.adminPermissions !== undefined && raw.adminPermissions !== null) {
|
|
117
|
+
throw new ValidationError('Legacy tokens cannot carry admin permissions');
|
|
118
|
+
}
|
|
119
|
+
if (raw.adminUserOwner !== undefined && raw.adminUserOwner !== null) {
|
|
120
|
+
throw new ValidationError('Legacy tokens cannot have an admin user owner');
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Assert that an admin-kind token body does not carry legacy-only fields
|
|
125
|
+
*/ const assertAdminKindFields = (attributes)=>{
|
|
126
|
+
const raw = attributes;
|
|
127
|
+
if (raw.type !== undefined && raw.type !== null) {
|
|
128
|
+
throw new ValidationError('Admin tokens cannot carry a legacy type (custom/read-only/full-access)');
|
|
129
|
+
}
|
|
130
|
+
if (raw.permissions !== undefined && raw.permissions !== null) {
|
|
131
|
+
throw new ValidationError('Admin tokens cannot carry legacy content-API permissions');
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Assert that admin permissions are valid
|
|
136
|
+
*/ const assertAdminPermissionsValidity = async (adminPermissions)=>{
|
|
137
|
+
if (!adminPermissions || adminPermissions.length === 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Validate that all actions exist in the admin action provider
|
|
141
|
+
const validActions = index.getService('permission').actionProvider.keys();
|
|
142
|
+
for (const perm of adminPermissions){
|
|
143
|
+
if (!validActions.includes(perm.action)) {
|
|
144
|
+
throw new ValidationError(`Unknown admin action: ${perm.action}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Use existing permission validation
|
|
148
|
+
await permission.validatePermissionsExist(adminPermissions);
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Enforce that every requested admin permission stays within the calling
|
|
152
|
+
* user's own permission ceiling, then return the clamped permissions.
|
|
153
|
+
*
|
|
154
|
+
* Super-admins bypass this (they hold every permission).
|
|
155
|
+
* When admin permissions are requested, an authenticated user is required (no bypass when user is missing).
|
|
156
|
+
*
|
|
157
|
+
* For each requested permission:
|
|
158
|
+
* - action + subject must match at least one user permission
|
|
159
|
+
* - properties.fields must be ⊆ user's properties.fields
|
|
160
|
+
* (if the user's permission defines no fields, all fields are allowed)
|
|
161
|
+
* - conditions are inherited from the user's matching permission(s);
|
|
162
|
+
* the caller cannot configure conditions on their own tokens
|
|
163
|
+
*
|
|
164
|
+
* Returns the permissions with conditions enforced from the user's role.
|
|
165
|
+
* Throws ValidationError if any permission exceeds the user's ceiling.
|
|
166
|
+
*
|
|
167
|
+
* Guaranteed postcondition: all returned permissions have conditions filtered to
|
|
168
|
+
* registered conditions only, regardless of the user type.
|
|
169
|
+
*/ const enforceAdminPermissionsCeiling = async (user, requestedPermissions)=>{
|
|
170
|
+
if (!requestedPermissions || requestedPermissions.length === 0) {
|
|
171
|
+
return requestedPermissions || [];
|
|
172
|
+
}
|
|
173
|
+
if (user === undefined || user === null) {
|
|
174
|
+
throw new ValidationError('Admin permission ceiling cannot be enforced without an authenticated user');
|
|
175
|
+
}
|
|
176
|
+
if (isSuperAdmin(user)) {
|
|
177
|
+
// Sanitize conditions even for super-admins so this function is a complete boundary.
|
|
178
|
+
// createApiTokenAdminPermissions also sanitizes, but relying on that downstream
|
|
179
|
+
// is fragile — any future call path that skips it would store invalid conditions.
|
|
180
|
+
const { conditionProvider } = index.getService('permission');
|
|
181
|
+
const sanitize = index$1.default.sanitizeConditions(conditionProvider);
|
|
182
|
+
return requestedPermissions.map((perm)=>{
|
|
183
|
+
const sanitized = sanitize({
|
|
184
|
+
...perm,
|
|
185
|
+
actionParameters: {}
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
...perm,
|
|
189
|
+
conditions: sanitized.conditions
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const userPermissions = await index.getService('permission').findUserPermissions(user);
|
|
194
|
+
const exceeding = [];
|
|
195
|
+
const clamped = requestedPermissions.map((requested)=>{
|
|
196
|
+
const requestedSubject = requested.subject || null;
|
|
197
|
+
// Find all user permissions matching action + subject
|
|
198
|
+
const matchingUserPerms = userPermissions.filter((userPerm)=>{
|
|
199
|
+
if (userPerm.action !== requested.action) return false;
|
|
200
|
+
const userSubject = userPerm.subject || null;
|
|
201
|
+
return requestedSubject === userSubject;
|
|
202
|
+
});
|
|
203
|
+
const label = requestedSubject !== null ? `${requested.action} on ${requestedSubject}` : requested.action;
|
|
204
|
+
if (matchingUserPerms.length === 0) {
|
|
205
|
+
exceeding.push(label);
|
|
206
|
+
return requested;
|
|
207
|
+
}
|
|
208
|
+
// --- Field-level ceiling ---
|
|
209
|
+
// If any matching user perm has no fields defined → all fields are allowed.
|
|
210
|
+
// Otherwise, effective user fields = union of all matching perms' fields.
|
|
211
|
+
const anyUserPermHasAllFields = matchingUserPerms.some((p)=>!p.properties?.fields || p.properties.fields.length === 0);
|
|
212
|
+
const requestedFields = requested.properties?.fields;
|
|
213
|
+
if (!anyUserPermHasAllFields) {
|
|
214
|
+
const effectiveUserFields = fp.uniq(matchingUserPerms.flatMap((p)=>p.properties?.fields || []));
|
|
215
|
+
// When the owner is field-restricted, omitting fields would widen access to all fields.
|
|
216
|
+
// Force explicit field selection so token scope can't exceed the owner's ceiling.
|
|
217
|
+
if (requestedFields === undefined || requestedFields === null) {
|
|
218
|
+
exceeding.push(`${label} (fields are required due to owner field restrictions)`);
|
|
219
|
+
return requested;
|
|
220
|
+
}
|
|
221
|
+
if (requestedFields.length > 0) {
|
|
222
|
+
const exceedingFields = requestedFields.filter((f)=>!effectiveUserFields.includes(f));
|
|
223
|
+
if (exceedingFields.length > 0) {
|
|
224
|
+
exceeding.push(`${label} (fields: ${exceedingFields.join(', ')})`);
|
|
225
|
+
return requested;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// --- Condition-level ceiling ---
|
|
230
|
+
// Conditions are always inherited from the user's matching permission(s).
|
|
231
|
+
// If any matching user perm is unconditional → token gets no conditions.
|
|
232
|
+
// Otherwise → union of conditions across matching perms.
|
|
233
|
+
const anyUserPermIsUnconditional = matchingUserPerms.some((p)=>!p.conditions || p.conditions.length === 0);
|
|
234
|
+
const enforcedConditions = anyUserPermIsUnconditional ? [] : fp.uniq(matchingUserPerms.flatMap((p)=>p.conditions || []));
|
|
235
|
+
return {
|
|
236
|
+
...requested,
|
|
237
|
+
conditions: enforcedConditions
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
if (exceeding.length > 0) {
|
|
241
|
+
throw new ValidationError(`Cannot assign admin permissions that exceed your own. ` + `Exceeding: ${exceeding.join(', ')}`);
|
|
242
|
+
}
|
|
243
|
+
return clamped;
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Create admin permissions for an API token
|
|
247
|
+
*/ const createApiTokenAdminPermissions = async (tokenId, permissions)=>{
|
|
248
|
+
const { conditionProvider } = index.getService('permission');
|
|
249
|
+
const sanitizeConditions = index$1.default.sanitizeConditions(conditionProvider);
|
|
250
|
+
const permissionsWithToken = permissions.map((perm)=>{
|
|
251
|
+
const permAsPermission = {
|
|
252
|
+
...perm,
|
|
253
|
+
actionParameters: {}
|
|
254
|
+
};
|
|
255
|
+
const sanitized = sanitizeConditions(permAsPermission);
|
|
256
|
+
return index$1.default.create({
|
|
257
|
+
...sanitized,
|
|
258
|
+
apiToken: tokenId,
|
|
259
|
+
role: null
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
const createdPermissions = await index.getService('permission').createMany(permissionsWithToken);
|
|
263
|
+
return createdPermissions;
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Fields to compare when checking if two permissions are equal
|
|
267
|
+
*/ const COMPARABLE_FIELDS = [
|
|
268
|
+
'conditions',
|
|
269
|
+
'properties',
|
|
270
|
+
'subject',
|
|
271
|
+
'action',
|
|
272
|
+
'actionParameters'
|
|
273
|
+
];
|
|
274
|
+
const pickComparableFields = fp.pick(COMPARABLE_FIELDS);
|
|
275
|
+
/**
|
|
276
|
+
* Helper to clean JSON (remove undefined values)
|
|
277
|
+
*/ const jsonClean = (data)=>JSON.parse(JSON.stringify(data));
|
|
278
|
+
/**
|
|
279
|
+
* Compare two permissions for equality
|
|
280
|
+
*/ const arePermissionsEqual = (p1, p2)=>{
|
|
281
|
+
if (p1.action === p2.action) {
|
|
282
|
+
return fp.isEqual(jsonClean(pickComparableFields(p1)), jsonClean(pickComparableFields(p2)));
|
|
69
283
|
}
|
|
284
|
+
return false;
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Assign admin permissions to an API token (similar to role permission assignment).
|
|
288
|
+
* ceilingUser is the user whose permissions act as the ceiling — always the token owner,
|
|
289
|
+
* regardless of who is making the request.
|
|
290
|
+
*/ const assignAdminPermissionsToToken = async (tokenId, permissions, ceilingUser)=>{
|
|
291
|
+
await permission.validatePermissionsExist(permissions);
|
|
292
|
+
const clampedPermissions = await enforceAdminPermissionsCeiling(ceilingUser, permissions);
|
|
293
|
+
const permissionsWithToken = clampedPermissions.map((perm)=>index$1.default.create({
|
|
294
|
+
...perm,
|
|
295
|
+
apiToken: tokenId,
|
|
296
|
+
role: null
|
|
297
|
+
}));
|
|
298
|
+
const existingPermissions = await index.getService('permission').findMany({
|
|
299
|
+
where: {
|
|
300
|
+
apiToken: {
|
|
301
|
+
id: tokenId
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const permissionsToAdd = fp.differenceWith(arePermissionsEqual, permissionsWithToken, existingPermissions);
|
|
306
|
+
const permissionsToDelete = fp.differenceWith(arePermissionsEqual, existingPermissions, permissionsWithToken);
|
|
307
|
+
if (permissionsToDelete.length > 0) {
|
|
308
|
+
await index.getService('permission').deleteByIds(permissionsToDelete.map(fp.prop('id')));
|
|
309
|
+
}
|
|
310
|
+
if (permissionsToAdd.length > 0) {
|
|
311
|
+
await createApiTokenAdminPermissions(tokenId, permissionsToAdd);
|
|
312
|
+
}
|
|
313
|
+
// Return all current permissions
|
|
314
|
+
const allCurrentPermissions = await index.getService('permission').findMany({
|
|
315
|
+
where: {
|
|
316
|
+
apiToken: {
|
|
317
|
+
id: tokenId
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
return allCurrentPermissions;
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Reconcile a token's admin permissions against the owner's current effective ceiling.
|
|
325
|
+
*
|
|
326
|
+
* Pure / sync — no DB calls. Returns two buckets:
|
|
327
|
+
* toDelete – permissions that are no longer within the user's scope (action/subject missing
|
|
328
|
+
* or requested fields exceed the allowed set)
|
|
329
|
+
* toUpdate – permissions that are still in scope but whose conditions must be re-clamped
|
|
330
|
+
* to the current union of the matching user permissions' conditions
|
|
331
|
+
*/ const reconcileTokenPermissionsToUserCeiling = (userPermissions, tokenPermissions)=>{
|
|
332
|
+
const toDelete = [];
|
|
333
|
+
const toUpdate = [];
|
|
334
|
+
tokenPermissions.forEach((tokenPerm)=>{
|
|
335
|
+
const tokenSubject = tokenPerm.subject || null;
|
|
336
|
+
const matchingUserPerms = userPermissions.filter((userPerm)=>userPerm.action === tokenPerm.action && (userPerm.subject || null) === tokenSubject);
|
|
337
|
+
if (matchingUserPerms.length === 0) {
|
|
338
|
+
toDelete.push(tokenPerm);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Field-level ceiling check (mirrors enforceAdminPermissionsCeiling)
|
|
342
|
+
const anyUserPermHasAllFields = matchingUserPerms.some((p)=>!p.properties?.fields || p.properties.fields.length === 0);
|
|
343
|
+
const tokenFields = tokenPerm.properties?.fields;
|
|
344
|
+
const fieldCeilingExceeded = !anyUserPermHasAllFields && tokenFields !== undefined && tokenFields !== null && tokenFields.length > 0 && (()=>{
|
|
345
|
+
const effectiveUserFields = fp.uniq(matchingUserPerms.flatMap((p)=>p.properties?.fields || []));
|
|
346
|
+
return tokenFields.some((f)=>!effectiveUserFields.includes(f));
|
|
347
|
+
})();
|
|
348
|
+
if (fieldCeilingExceeded) {
|
|
349
|
+
toDelete.push(tokenPerm);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// Condition: force conditions to be the ones of the user permission(s)
|
|
353
|
+
const anyUserPermIsUnconditional = matchingUserPerms.some((p)=>!p.conditions || p.conditions.length === 0);
|
|
354
|
+
const enforcedConditions = anyUserPermIsUnconditional ? [] : fp.uniq(matchingUserPerms.flatMap((p)=>p.conditions || []));
|
|
355
|
+
const currentConditions = tokenPerm.conditions || [];
|
|
356
|
+
const conditionsChanged = enforcedConditions.length !== currentConditions.length || enforcedConditions.some((c)=>!currentConditions.includes(c));
|
|
357
|
+
if (conditionsChanged) {
|
|
358
|
+
toUpdate.push({
|
|
359
|
+
id: tokenPerm.id,
|
|
360
|
+
conditions: enforcedConditions
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
});
|
|
70
364
|
return {
|
|
71
|
-
|
|
72
|
-
|
|
365
|
+
toDelete,
|
|
366
|
+
toUpdate
|
|
73
367
|
};
|
|
74
368
|
};
|
|
75
369
|
/**
|
|
76
|
-
*
|
|
77
|
-
|
|
370
|
+
* Re-sync all admin token permissions for a given user against their current effective ceiling.
|
|
371
|
+
*
|
|
372
|
+
* Skips super-admins (no ceiling). For each admin token owned by the user:
|
|
373
|
+
* - Deletes permissions that are no longer within the user's scope
|
|
374
|
+
* - Updates conditions on permissions whose conditions have drifted from the role's current set
|
|
375
|
+
*/ const syncApiTokenPermissionsForUser = async (userId)=>{
|
|
376
|
+
const user = await strapi.db.query('admin::user').findOne({
|
|
377
|
+
where: {
|
|
378
|
+
id: userId
|
|
379
|
+
},
|
|
380
|
+
populate: [
|
|
381
|
+
'roles'
|
|
382
|
+
]
|
|
383
|
+
});
|
|
384
|
+
if (user === null || user === undefined) return;
|
|
385
|
+
if (isSuperAdmin(user)) return;
|
|
386
|
+
const userEffectivePermissions = await index.getService('permission').findUserPermissions(user);
|
|
387
|
+
const tokens = await strapi.db.query('admin::api-token').findMany({
|
|
388
|
+
where: {
|
|
389
|
+
kind: 'admin',
|
|
390
|
+
adminUserOwner: {
|
|
391
|
+
id: userId
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
populate: [
|
|
395
|
+
'adminPermissions'
|
|
396
|
+
]
|
|
397
|
+
});
|
|
398
|
+
const tokensWithPermissions = tokens.filter((token)=>Array.isArray(token.adminPermissions) && token.adminPermissions.length > 0);
|
|
399
|
+
for (const token of tokensWithPermissions){
|
|
400
|
+
const tokenPermissions = token.adminPermissions;
|
|
401
|
+
const { toDelete, toUpdate } = reconcileTokenPermissionsToUserCeiling(userEffectivePermissions, tokenPermissions);
|
|
402
|
+
if (toDelete.length > 0) {
|
|
403
|
+
await index.getService('permission').deleteByIds(toDelete.map((p)=>p.id));
|
|
404
|
+
}
|
|
405
|
+
for (const { id, conditions } of toUpdate){
|
|
406
|
+
await strapi.db.query('admin::permission').update({
|
|
407
|
+
where: {
|
|
408
|
+
id
|
|
409
|
+
},
|
|
410
|
+
data: {
|
|
411
|
+
conditions
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* Re-sync admin token permissions for all admin users who hold a given role.
|
|
419
|
+
* Called after role permissions are updated.
|
|
420
|
+
*/ const syncApiTokenPermissionsForRole = async (roleId)=>{
|
|
421
|
+
const users = await strapi.db.query('admin::user').findMany({
|
|
422
|
+
where: {
|
|
423
|
+
roles: {
|
|
424
|
+
id: roleId
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
populate: [
|
|
428
|
+
'roles'
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
await Promise.allSettled(users.map((user)=>syncApiTokenPermissionsForUser(user.id)));
|
|
432
|
+
};
|
|
433
|
+
/**
|
|
434
|
+
* Flatten a token's database permissions objects to an array of strings
|
|
435
|
+
*/ const flattenTokenPermissions = (permissions)=>{
|
|
436
|
+
return fp.isArray(permissions) ? fp.map('action', permissions) : [];
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* Get a token.
|
|
440
|
+
* By default the plaintext accessKey is NOT included.
|
|
441
|
+
* Pass { includeDecryptedKey: true } to decrypt and return it (owner-only paths).
|
|
442
|
+
*/ const getBy = async (whereParams = {}, options = {})=>{
|
|
78
443
|
if (Object.keys(whereParams).length === 0) {
|
|
79
444
|
return null;
|
|
80
445
|
}
|
|
446
|
+
const { includeDecryptedKey = false } = options;
|
|
447
|
+
const selectFields = includeDecryptedKey ? [
|
|
448
|
+
...SELECT_FIELDS,
|
|
449
|
+
'encryptedKey'
|
|
450
|
+
] : SELECT_FIELDS;
|
|
81
451
|
const token = await strapi.db.query('admin::api-token').findOne({
|
|
82
|
-
select:
|
|
83
|
-
...SELECT_FIELDS,
|
|
84
|
-
'encryptedKey'
|
|
85
|
-
],
|
|
452
|
+
select: selectFields,
|
|
86
453
|
populate: POPULATE_FIELDS,
|
|
87
454
|
where: whereParams
|
|
88
455
|
});
|
|
89
456
|
if (!token) {
|
|
90
457
|
return token;
|
|
91
458
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
459
|
+
// Tokens created before kind introduction case: force kind to be content-api
|
|
460
|
+
const computedKind = token.kind ?? 'content-api';
|
|
461
|
+
const result = fp.omit([
|
|
462
|
+
'accessKey',
|
|
463
|
+
'encryptedKey',
|
|
464
|
+
'type',
|
|
465
|
+
'permissions',
|
|
466
|
+
'adminPermissions',
|
|
467
|
+
'adminUserOwner'
|
|
468
|
+
], token);
|
|
469
|
+
if (computedKind === 'content-api') {
|
|
470
|
+
Object.assign(result, {
|
|
471
|
+
kind: 'content-api',
|
|
472
|
+
type: token.type,
|
|
473
|
+
permissions: flattenTokenPermissions(token.permissions)
|
|
474
|
+
});
|
|
475
|
+
} else if (computedKind === 'admin') {
|
|
476
|
+
Object.assign(result, {
|
|
477
|
+
kind: 'admin',
|
|
478
|
+
adminPermissions: token.adminPermissions,
|
|
479
|
+
adminUserOwner: toAdminTokenOwner(token.adminUserOwner)
|
|
480
|
+
});
|
|
95
481
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
482
|
+
if (includeDecryptedKey && token.encryptedKey) {
|
|
483
|
+
Object.assign(result, {
|
|
484
|
+
accessKey: index.getService('encryption').decrypt(token.encryptedKey)
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
101
488
|
};
|
|
102
489
|
/**
|
|
103
490
|
* Check if token exists
|
|
@@ -119,54 +506,116 @@ const getExpirationFields = (lifespan)=>{
|
|
|
119
506
|
throw new ValidationError('lifespan must be a positive number or null');
|
|
120
507
|
}
|
|
121
508
|
return {
|
|
122
|
-
lifespan: lifespan
|
|
509
|
+
lifespan: lifespan ?? null,
|
|
123
510
|
expiresAt: lifespan ? Date.now() + lifespan : null
|
|
124
511
|
};
|
|
125
512
|
};
|
|
126
513
|
/**
|
|
127
514
|
* Create a token and its permissions
|
|
128
|
-
*/ const create = async (attributes)=>{
|
|
515
|
+
*/ const create = async (attributes, callingUser)=>{
|
|
129
516
|
const encryptionService = index.getService('encryption');
|
|
130
517
|
const accessKey = crypto.randomBytes(128).toString('hex');
|
|
131
518
|
const encryptedKey = encryptionService.encrypt(accessKey);
|
|
132
|
-
assertCustomTokenPermissionsValidity(attributes.type, attributes.permissions);
|
|
133
519
|
assertValidLifespan(attributes.lifespan);
|
|
134
|
-
|
|
520
|
+
if (attributes.kind === 'content-api') {
|
|
521
|
+
const castedContentApiApiTokenBody = attributes;
|
|
522
|
+
assertLegacyKindFields(castedContentApiApiTokenBody);
|
|
523
|
+
assertCustomTokenPermissionsValidity(castedContentApiApiTokenBody.type, castedContentApiApiTokenBody.permissions);
|
|
524
|
+
// content api tokens have no owner
|
|
525
|
+
const apiToken = await strapi.db.query('admin::api-token').create({
|
|
526
|
+
select: SELECT_FIELDS,
|
|
527
|
+
populate: POPULATE_FIELDS,
|
|
528
|
+
data: {
|
|
529
|
+
...fp.omit([
|
|
530
|
+
'permissions',
|
|
531
|
+
'adminPermissions',
|
|
532
|
+
'adminUserOwner'
|
|
533
|
+
], attributes),
|
|
534
|
+
accessKey: hash(accessKey),
|
|
535
|
+
encryptedKey,
|
|
536
|
+
adminUserOwner: null,
|
|
537
|
+
...getExpirationFields(castedContentApiApiTokenBody.lifespan ?? null)
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
const result = {
|
|
541
|
+
...apiToken,
|
|
542
|
+
accessKey
|
|
543
|
+
};
|
|
544
|
+
// If this is a custom type token, create the related content-API permissions
|
|
545
|
+
if (castedContentApiApiTokenBody.type === constants.API_TOKEN_TYPE.CUSTOM) {
|
|
546
|
+
// TODO: createMany doesn't seem to create relation properly, implement a better way rather than a ton of queries
|
|
547
|
+
await Promise.all(fp.uniq(castedContentApiApiTokenBody.permissions).map((action)=>strapi.db.query('admin::api-token-permission').create({
|
|
548
|
+
data: {
|
|
549
|
+
action,
|
|
550
|
+
token: apiToken
|
|
551
|
+
}
|
|
552
|
+
})));
|
|
553
|
+
const currentPermissions = await strapi.db.query('admin::api-token').load(apiToken, 'permissions');
|
|
554
|
+
if (currentPermissions) {
|
|
555
|
+
Object.assign(result, {
|
|
556
|
+
permissions: flattenTokenPermissions(currentPermissions)
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// Casted to any to avoid complex type duplication
|
|
561
|
+
return fp.omit([
|
|
562
|
+
'adminPermissions',
|
|
563
|
+
'adminUserOwner'
|
|
564
|
+
], result);
|
|
565
|
+
}
|
|
566
|
+
// kind === 'admin'
|
|
567
|
+
assertAdminKindFields(attributes);
|
|
568
|
+
const castedAdminTokenBody = attributes;
|
|
569
|
+
await assertAdminPermissionsValidity(castedAdminTokenBody.adminPermissions);
|
|
570
|
+
const clampedAdminPermissions = await enforceAdminPermissionsCeiling(callingUser, castedAdminTokenBody.adminPermissions);
|
|
571
|
+
// Owner: when explicitly provided, it must match the caller.
|
|
572
|
+
// When omitted, always defaults to the calling user (including super admins).
|
|
573
|
+
let ownerId;
|
|
574
|
+
if (castedAdminTokenBody.adminUserOwner !== undefined && castedAdminTokenBody.adminUserOwner !== null) {
|
|
575
|
+
await assertOwnerMatchesCallingUser(castedAdminTokenBody.adminUserOwner, callingUser);
|
|
576
|
+
ownerId = castedAdminTokenBody.adminUserOwner;
|
|
577
|
+
} else {
|
|
578
|
+
if (callingUser === undefined || callingUser === null) {
|
|
579
|
+
throw new ValidationError('Creating an admin token requires an authenticated admin user');
|
|
580
|
+
}
|
|
581
|
+
ownerId = callingUser.id;
|
|
582
|
+
}
|
|
135
583
|
const apiToken = await strapi.db.query('admin::api-token').create({
|
|
136
584
|
select: SELECT_FIELDS,
|
|
137
585
|
populate: POPULATE_FIELDS,
|
|
138
586
|
data: {
|
|
139
|
-
...fp.omit(
|
|
587
|
+
...fp.omit([
|
|
588
|
+
'permissions',
|
|
589
|
+
'adminPermissions',
|
|
590
|
+
'adminUserOwner'
|
|
591
|
+
], attributes),
|
|
140
592
|
accessKey: hash(accessKey),
|
|
141
593
|
encryptedKey,
|
|
142
|
-
|
|
594
|
+
adminUserOwner: ownerId,
|
|
595
|
+
...getExpirationFields(castedAdminTokenBody.lifespan ?? null)
|
|
143
596
|
}
|
|
144
597
|
});
|
|
145
598
|
const result = {
|
|
146
599
|
...apiToken,
|
|
147
600
|
accessKey
|
|
148
601
|
};
|
|
149
|
-
//
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// data: attributes.permissions.map(action => ({ action, token: apiToken })),
|
|
155
|
-
// });
|
|
156
|
-
await Promise.all(fp.uniq(attributes.permissions).map((action)=>strapi.db.query('admin::api-token-permission').create({
|
|
157
|
-
data: {
|
|
158
|
-
action,
|
|
159
|
-
token: apiToken
|
|
160
|
-
}
|
|
161
|
-
})));
|
|
162
|
-
const currentPermissions = await strapi.db.query('admin::api-token').load(apiToken, 'permissions');
|
|
163
|
-
if (currentPermissions) {
|
|
602
|
+
// Handle admin permissions (using ceiling-clamped permissions with inherited conditions)
|
|
603
|
+
if (clampedAdminPermissions.length > 0) {
|
|
604
|
+
await createApiTokenAdminPermissions(apiToken.id, clampedAdminPermissions);
|
|
605
|
+
const currentAdminPermissions = await strapi.db.query('admin::api-token').load(apiToken, 'adminPermissions');
|
|
606
|
+
if (currentAdminPermissions) {
|
|
164
607
|
Object.assign(result, {
|
|
165
|
-
|
|
608
|
+
adminPermissions: currentAdminPermissions
|
|
166
609
|
});
|
|
167
610
|
}
|
|
168
611
|
}
|
|
169
|
-
|
|
612
|
+
// Casted to any to avoid complex type duplication
|
|
613
|
+
return {
|
|
614
|
+
...fp.omit([
|
|
615
|
+
'permissions'
|
|
616
|
+
], result),
|
|
617
|
+
adminUserOwner: toAdminTokenOwner(result.adminUserOwner)
|
|
618
|
+
};
|
|
170
619
|
};
|
|
171
620
|
const regenerate = async (id)=>{
|
|
172
621
|
const accessKey = crypto.randomBytes(128).toString('hex');
|
|
@@ -208,50 +657,114 @@ For security reasons, prefer storing the secret in an environment variable and r
|
|
|
208
657
|
}
|
|
209
658
|
};
|
|
210
659
|
/**
|
|
211
|
-
* Return a list of
|
|
212
|
-
|
|
660
|
+
* Return a list of tokens visible to the calling user.
|
|
661
|
+
* Super-admins see all tokens; regular admins see only ownerless tokens and their own.
|
|
662
|
+
*/ const list = async (callingUser, { filter } = {})=>{
|
|
663
|
+
const ownershipWhere = isSuperAdmin(callingUser) ? {} : {
|
|
664
|
+
$or: [
|
|
665
|
+
{
|
|
666
|
+
adminUserOwner: null
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
adminUserOwner: {
|
|
670
|
+
id: callingUser.id
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
]
|
|
674
|
+
};
|
|
675
|
+
// Tokens without a persisted kind are content-api tokens (pre-migration rows).
|
|
676
|
+
let kindWhere = {};
|
|
677
|
+
if (filter?.kind === 'content-api') {
|
|
678
|
+
kindWhere = {
|
|
679
|
+
$or: [
|
|
680
|
+
{
|
|
681
|
+
kind: 'content-api'
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
kind: {
|
|
685
|
+
$null: true
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
]
|
|
689
|
+
};
|
|
690
|
+
} else if (filter?.kind !== undefined) {
|
|
691
|
+
kindWhere = {
|
|
692
|
+
kind: filter.kind
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const where = {
|
|
696
|
+
...ownershipWhere,
|
|
697
|
+
...kindWhere
|
|
698
|
+
};
|
|
213
699
|
const tokens = await strapi.db.query('admin::api-token').findMany({
|
|
214
700
|
select: SELECT_FIELDS,
|
|
215
701
|
populate: POPULATE_FIELDS,
|
|
216
702
|
orderBy: {
|
|
217
703
|
name: 'ASC'
|
|
218
|
-
}
|
|
704
|
+
},
|
|
705
|
+
where
|
|
219
706
|
});
|
|
220
707
|
if (!tokens) {
|
|
221
708
|
return tokens;
|
|
222
709
|
}
|
|
223
|
-
return tokens.map((token)=>
|
|
710
|
+
return tokens.map((token)=>token.kind === null || token.kind === 'content-api' ? fp.omit([
|
|
711
|
+
'adminPermissions',
|
|
712
|
+
'adminUserOwner'
|
|
713
|
+
], {
|
|
714
|
+
...token,
|
|
715
|
+
// Tokens created before kind introduction case: force kind to be content-api
|
|
716
|
+
kind: 'content-api',
|
|
717
|
+
permissions: flattenTokenPermissions(token.permissions)
|
|
718
|
+
}) : {
|
|
719
|
+
...fp.omit([
|
|
720
|
+
'permissions'
|
|
721
|
+
], token),
|
|
722
|
+
adminUserOwner: toAdminTokenOwner(token.adminUserOwner)
|
|
723
|
+
});
|
|
224
724
|
};
|
|
225
725
|
/**
|
|
226
726
|
* Revoke (delete) a token
|
|
227
727
|
*/ const revoke = async (id)=>{
|
|
228
|
-
|
|
728
|
+
const token = await strapi.db.query('admin::api-token').findOne({
|
|
729
|
+
where: {
|
|
730
|
+
id
|
|
731
|
+
},
|
|
732
|
+
select: [
|
|
733
|
+
'id'
|
|
734
|
+
],
|
|
735
|
+
populate: [
|
|
736
|
+
'adminPermissions'
|
|
737
|
+
]
|
|
738
|
+
});
|
|
739
|
+
if (token !== null && token !== undefined) {
|
|
740
|
+
const permissionIds = (token.adminPermissions ?? []).map((p)=>p.id).filter((permId)=>permId !== null && permId !== undefined);
|
|
741
|
+
if (permissionIds.length > 0) {
|
|
742
|
+
await index.getService('permission').deleteByIds(permissionIds);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const deletedToken = await strapi.db.query('admin::api-token').delete({
|
|
229
746
|
select: SELECT_FIELDS,
|
|
230
747
|
populate: POPULATE_FIELDS,
|
|
231
748
|
where: {
|
|
232
749
|
id
|
|
233
750
|
}
|
|
234
751
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
/**
|
|
244
|
-
* Retrieve a token by name
|
|
245
|
-
*/ const getByName = async (name)=>{
|
|
246
|
-
return getBy({
|
|
247
|
-
name
|
|
248
|
-
});
|
|
752
|
+
if (deletedToken === null || deletedToken === undefined || deletedToken.kind !== 'admin') {
|
|
753
|
+
return deletedToken;
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
...deletedToken,
|
|
757
|
+
adminUserOwner: toAdminTokenOwner(deletedToken.adminUserOwner)
|
|
758
|
+
};
|
|
249
759
|
};
|
|
250
760
|
/**
|
|
251
761
|
* Update a token and its permissions
|
|
252
762
|
*/ const update = async (id, attributes)=>{
|
|
253
|
-
// retrieve token without permissions
|
|
254
763
|
const originalToken = await strapi.db.query('admin::api-token').findOne({
|
|
764
|
+
select: SELECT_FIELDS,
|
|
765
|
+
populate: [
|
|
766
|
+
'adminUserOwner'
|
|
767
|
+
],
|
|
255
768
|
where: {
|
|
256
769
|
id
|
|
257
770
|
}
|
|
@@ -259,55 +772,112 @@ For security reasons, prefer storing the secret in an environment variable and r
|
|
|
259
772
|
if (!originalToken) {
|
|
260
773
|
throw new NotFoundError('Token not found');
|
|
261
774
|
}
|
|
262
|
-
const
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
assertCustomTokenPermissionsValidity(attributes.type || originalToken.type, attributes.permissions || originalToken.permissions);
|
|
775
|
+
const raw = attributes;
|
|
776
|
+
// kind is immutable after creation
|
|
777
|
+
if (raw.kind !== undefined && raw.kind !== null && raw.kind !== originalToken.kind) {
|
|
778
|
+
throw new ValidationError('kind is immutable after creation');
|
|
267
779
|
}
|
|
268
780
|
assertValidLifespan(attributes.lifespan);
|
|
781
|
+
let clampedAdminPermissions;
|
|
782
|
+
let tokenOwnerUser;
|
|
783
|
+
if (originalToken.kind === 'content-api') {
|
|
784
|
+
assertLegacyKindFields(attributes);
|
|
785
|
+
const incomingType = raw.type;
|
|
786
|
+
const incomingPermissions = raw.permissions;
|
|
787
|
+
const resolvedType = incomingType ?? originalToken.type;
|
|
788
|
+
const changingTypeToCustom = incomingType === constants.API_TOKEN_TYPE.CUSTOM && originalToken.type !== constants.API_TOKEN_TYPE.CUSTOM;
|
|
789
|
+
// Only re-validate if permissions or type are being changed
|
|
790
|
+
if (incomingPermissions !== undefined || changingTypeToCustom) {
|
|
791
|
+
assertCustomTokenPermissionsValidity(resolvedType, incomingPermissions ?? originalToken.permissions);
|
|
792
|
+
}
|
|
793
|
+
} else if (originalToken.kind === 'admin') {
|
|
794
|
+
assertAdminKindFields(attributes);
|
|
795
|
+
const incomingAdminPermissions = raw.adminPermissions;
|
|
796
|
+
if (incomingAdminPermissions !== undefined) {
|
|
797
|
+
await assertAdminPermissionsValidity(incomingAdminPermissions);
|
|
798
|
+
// Ceiling is always the owner's permissions, not the calling user's.
|
|
799
|
+
// A super admin editing another user's token must not overflow that user's scope.
|
|
800
|
+
const ownerId = getOwnerId(originalToken);
|
|
801
|
+
const resolvedOwner = await index.getService('user').findOne(ownerId);
|
|
802
|
+
if (resolvedOwner === null || resolvedOwner === undefined) {
|
|
803
|
+
throw new ValidationError('Token owner no longer exists');
|
|
804
|
+
}
|
|
805
|
+
tokenOwnerUser = resolvedOwner;
|
|
806
|
+
clampedAdminPermissions = await enforceAdminPermissionsCeiling(tokenOwnerUser, incomingAdminPermissions);
|
|
807
|
+
}
|
|
808
|
+
const incomingAdminUserOwner = raw.adminUserOwner;
|
|
809
|
+
if (incomingAdminUserOwner !== undefined) {
|
|
810
|
+
// Owner is immutable; the provided value must match the existing one
|
|
811
|
+
const existingOwner = originalToken.adminUserOwner;
|
|
812
|
+
const existingOwnerId = existingOwner === null || existingOwner === undefined ? null : String(typeof existingOwner === 'object' ? existingOwner.id : existingOwner);
|
|
813
|
+
const requestedOwnerId = incomingAdminUserOwner === null ? null : String(incomingAdminUserOwner);
|
|
814
|
+
if (requestedOwnerId !== existingOwnerId) {
|
|
815
|
+
throw new ValidationError('adminUserOwner cannot be changed on update');
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
269
819
|
const updatedToken = await strapi.db.query('admin::api-token').update({
|
|
270
820
|
select: SELECT_FIELDS,
|
|
271
821
|
where: {
|
|
272
822
|
id
|
|
273
823
|
},
|
|
274
|
-
|
|
824
|
+
// kind is immutable — strip it along with relation fields so the DB write is clean
|
|
825
|
+
data: fp.omit([
|
|
826
|
+
'kind',
|
|
827
|
+
'permissions',
|
|
828
|
+
'adminPermissions',
|
|
829
|
+
'adminUserOwner'
|
|
830
|
+
], attributes)
|
|
275
831
|
});
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
832
|
+
if (originalToken.kind === 'content-api') {
|
|
833
|
+
const incomingPermissions = raw.permissions;
|
|
834
|
+
// custom tokens need to have their permissions updated as well
|
|
835
|
+
if (updatedToken.type === constants.API_TOKEN_TYPE.CUSTOM && incomingPermissions !== undefined) {
|
|
836
|
+
const currentPermissionsResult = await strapi.db.query('admin::api-token').load(updatedToken, 'permissions');
|
|
837
|
+
const currentPermissions = fp.map('action', currentPermissionsResult || []);
|
|
838
|
+
const newPermissions = fp.uniq(incomingPermissions || []);
|
|
839
|
+
const actionsToDelete = fp.difference(currentPermissions, newPermissions);
|
|
840
|
+
const actionsToAdd = fp.difference(newPermissions, currentPermissions);
|
|
841
|
+
// TODO: improve efficiency here
|
|
842
|
+
await Promise.all(actionsToDelete.map((action)=>strapi.db.query('admin::api-token-permission').delete({
|
|
843
|
+
where: {
|
|
844
|
+
action,
|
|
845
|
+
token: id
|
|
846
|
+
}
|
|
847
|
+
})));
|
|
848
|
+
// TODO: improve efficiency here
|
|
849
|
+
await Promise.all(actionsToAdd.map((action)=>strapi.db.query('admin::api-token-permission').create({
|
|
850
|
+
data: {
|
|
851
|
+
action,
|
|
852
|
+
token: id
|
|
853
|
+
}
|
|
854
|
+
})));
|
|
855
|
+
} else if (updatedToken.type !== constants.API_TOKEN_TYPE.CUSTOM) {
|
|
856
|
+
await strapi.db.query('admin::api-token-permission').delete({
|
|
286
857
|
where: {
|
|
287
|
-
action,
|
|
288
858
|
token: id
|
|
289
859
|
}
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
});
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
const permissionsFromDb = await strapi.db.query('admin::api-token').load(updatedToken, 'permissions');
|
|
863
|
+
return {
|
|
864
|
+
...updatedToken,
|
|
865
|
+
permissions: permissionsFromDb ? permissionsFromDb.map((p)=>p.action) : undefined
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
// kind === 'admin'
|
|
869
|
+
if (clampedAdminPermissions !== undefined) {
|
|
870
|
+
if (tokenOwnerUser === undefined) {
|
|
871
|
+
throw new ValidationError('Updating admin permissions requires a resolved token owner');
|
|
872
|
+
}
|
|
873
|
+
await assignAdminPermissionsToToken(id, clampedAdminPermissions, tokenOwnerUser);
|
|
305
874
|
}
|
|
306
|
-
|
|
307
|
-
const
|
|
875
|
+
const adminPermissionsFromDb = await strapi.db.query('admin::api-token').load(updatedToken, 'adminPermissions');
|
|
876
|
+
const adminUserOwnerFromDb = await strapi.db.query('admin::api-token').load(updatedToken, 'adminUserOwner');
|
|
308
877
|
return {
|
|
309
878
|
...updatedToken,
|
|
310
|
-
|
|
879
|
+
adminPermissions: adminPermissionsFromDb || [],
|
|
880
|
+
adminUserOwner: toAdminTokenOwner(adminUserOwnerFromDb)
|
|
311
881
|
};
|
|
312
882
|
};
|
|
313
883
|
const count = async (where = {})=>{
|
|
@@ -315,17 +885,151 @@ const count = async (where = {})=>{
|
|
|
315
885
|
where
|
|
316
886
|
});
|
|
317
887
|
};
|
|
888
|
+
/**
|
|
889
|
+
* Delete all admin API tokens owned by the given user, including their associated admin permissions.
|
|
890
|
+
* Called when the owner user is deleted so tokens don't linger with a dangling owner FK.
|
|
891
|
+
*/ const deleteAdminTokensForUser = async (userId)=>{
|
|
892
|
+
const tokens = await strapi.db.query('admin::api-token').findMany({
|
|
893
|
+
where: {
|
|
894
|
+
kind: 'admin',
|
|
895
|
+
adminUserOwner: {
|
|
896
|
+
id: userId
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
select: [
|
|
900
|
+
'id'
|
|
901
|
+
],
|
|
902
|
+
populate: [
|
|
903
|
+
'adminPermissions'
|
|
904
|
+
]
|
|
905
|
+
});
|
|
906
|
+
for (const token of tokens){
|
|
907
|
+
const permissionIds = (token.adminPermissions ?? []).map((p)=>p.id).filter((id)=>id !== null && id !== undefined);
|
|
908
|
+
if (permissionIds.length > 0) {
|
|
909
|
+
await index.getService('permission').deleteByIds(permissionIds);
|
|
910
|
+
}
|
|
911
|
+
await strapi.db.query('admin::api-token').delete({
|
|
912
|
+
where: {
|
|
913
|
+
id: token.id
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
function createTokenService(kind) {
|
|
919
|
+
const shared = {
|
|
920
|
+
hash,
|
|
921
|
+
checkSaltIsDefined,
|
|
922
|
+
getByAccessKey: (accessKeyHash, options)=>getBy({
|
|
923
|
+
accessKey: accessKeyHash
|
|
924
|
+
}, options),
|
|
925
|
+
countAll: count,
|
|
926
|
+
reconcileTokenPermissionsToUserCeiling
|
|
927
|
+
};
|
|
928
|
+
if (kind === 'content-api') {
|
|
929
|
+
const svc = {
|
|
930
|
+
...shared,
|
|
931
|
+
create: (attributes, callingUser)=>create({
|
|
932
|
+
...attributes,
|
|
933
|
+
kind: 'content-api'
|
|
934
|
+
}, callingUser),
|
|
935
|
+
list: (callingUser)=>list(callingUser, {
|
|
936
|
+
filter: {
|
|
937
|
+
kind: 'content-api'
|
|
938
|
+
}
|
|
939
|
+
}),
|
|
940
|
+
getById: (id, options)=>getBy({
|
|
941
|
+
$and: [
|
|
942
|
+
{
|
|
943
|
+
id
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
$or: [
|
|
947
|
+
{
|
|
948
|
+
kind: 'content-api'
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
kind: {
|
|
952
|
+
$null: true
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
]
|
|
956
|
+
}
|
|
957
|
+
]
|
|
958
|
+
}, options),
|
|
959
|
+
getByName: (name, options)=>getBy({
|
|
960
|
+
$and: [
|
|
961
|
+
{
|
|
962
|
+
name
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
$or: [
|
|
966
|
+
{
|
|
967
|
+
kind: 'content-api'
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
kind: {
|
|
971
|
+
$null: true
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
]
|
|
975
|
+
}
|
|
976
|
+
]
|
|
977
|
+
}, options),
|
|
978
|
+
update: (id, attributes)=>update(id, attributes),
|
|
979
|
+
revoke: (id)=>revoke(id),
|
|
980
|
+
regenerate: (id)=>regenerate(id),
|
|
981
|
+
exists,
|
|
982
|
+
count
|
|
983
|
+
};
|
|
984
|
+
return svc;
|
|
985
|
+
}
|
|
986
|
+
const svc = {
|
|
987
|
+
...shared,
|
|
988
|
+
create: (attributes, callingUser)=>create({
|
|
989
|
+
...attributes,
|
|
990
|
+
kind: 'admin'
|
|
991
|
+
}, callingUser),
|
|
992
|
+
list: (callingUser)=>list(callingUser, {
|
|
993
|
+
filter: {
|
|
994
|
+
kind: 'admin'
|
|
995
|
+
}
|
|
996
|
+
}),
|
|
997
|
+
getById: (id, options)=>getBy({
|
|
998
|
+
id,
|
|
999
|
+
kind: 'admin'
|
|
1000
|
+
}, options),
|
|
1001
|
+
getByName: (name, options)=>getBy({
|
|
1002
|
+
name,
|
|
1003
|
+
kind: 'admin'
|
|
1004
|
+
}, options),
|
|
1005
|
+
update: (id, attributes)=>update(id, attributes),
|
|
1006
|
+
revoke: (id)=>revoke(id),
|
|
1007
|
+
regenerate: (id)=>regenerate(id),
|
|
1008
|
+
exists,
|
|
1009
|
+
count,
|
|
1010
|
+
assignAdminPermissionsToToken,
|
|
1011
|
+
syncPermissionsForUser: syncApiTokenPermissionsForUser,
|
|
1012
|
+
syncPermissionsForRole: syncApiTokenPermissionsForRole,
|
|
1013
|
+
deleteTokensForUser: deleteAdminTokensForUser
|
|
1014
|
+
};
|
|
1015
|
+
return svc;
|
|
1016
|
+
}
|
|
318
1017
|
|
|
1018
|
+
exports.assignAdminPermissionsToToken = assignAdminPermissionsToToken;
|
|
319
1019
|
exports.checkSaltIsDefined = checkSaltIsDefined;
|
|
320
1020
|
exports.count = count;
|
|
321
1021
|
exports.create = create;
|
|
1022
|
+
exports.createTokenService = createTokenService;
|
|
1023
|
+
exports.deleteAdminTokensForUser = deleteAdminTokensForUser;
|
|
1024
|
+
exports.enforceAdminPermissionsCeiling = enforceAdminPermissionsCeiling;
|
|
322
1025
|
exports.exists = exists;
|
|
323
1026
|
exports.getBy = getBy;
|
|
324
|
-
exports.getById = getById;
|
|
325
|
-
exports.getByName = getByName;
|
|
326
1027
|
exports.hash = hash;
|
|
327
1028
|
exports.list = list;
|
|
1029
|
+
exports.reconcileTokenPermissionsToUserCeiling = reconcileTokenPermissionsToUserCeiling;
|
|
328
1030
|
exports.regenerate = regenerate;
|
|
329
1031
|
exports.revoke = revoke;
|
|
1032
|
+
exports.syncApiTokenPermissionsForRole = syncApiTokenPermissionsForRole;
|
|
1033
|
+
exports.syncApiTokenPermissionsForUser = syncApiTokenPermissionsForUser;
|
|
330
1034
|
exports.update = update;
|
|
331
1035
|
//# sourceMappingURL=api-token.js.map
|