@strapi/plugin-users-permissions 4.6.0-beta.2 → 4.6.1

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.
@@ -1,13 +1,13 @@
1
1
  import { useCallback, useReducer, useEffect, useRef } from 'react';
2
- import { useNotification } from '@strapi/helper-plugin';
2
+ import { useNotification, useFetchClient } from '@strapi/helper-plugin';
3
3
  import reducer, { initialState } from './reducer';
4
- import axiosInstance from '../../utils/axiosInstance';
5
4
  import pluginId from '../../pluginId';
6
5
 
7
6
  const useFetchRole = (id) => {
8
7
  const [state, dispatch] = useReducer(reducer, initialState);
9
8
  const toggleNotification = useNotification();
10
9
  const isMounted = useRef(null);
10
+ const { get } = useFetchClient();
11
11
 
12
12
  useEffect(() => {
13
13
  isMounted.current = true;
@@ -29,7 +29,7 @@ const useFetchRole = (id) => {
29
29
  try {
30
30
  const {
31
31
  data: { role },
32
- } = await axiosInstance.get(`/${pluginId}/roles/${roleId}`);
32
+ } = await get(`/${pluginId}/roles/${roleId}`);
33
33
 
34
34
  // Prevent updating state on an unmounted component
35
35
  if (isMounted.current) {
@@ -1,10 +1,9 @@
1
1
  import { useCallback, useEffect, useReducer } from 'react';
2
- import { useNotification } from '@strapi/helper-plugin';
2
+ import { useNotification, useFetchClient } from '@strapi/helper-plugin';
3
3
  import { get } from 'lodash';
4
4
  import init from './init';
5
5
  import pluginId from '../../pluginId';
6
6
  import { cleanPermissions } from '../../utils';
7
- import axiosInstance from '../../utils/axiosInstance';
8
7
  import reducer, { initialState } from './reducer';
9
8
 
10
9
  const usePlugins = (shouldFetchData = true) => {
@@ -12,6 +11,7 @@ const usePlugins = (shouldFetchData = true) => {
12
11
  const [{ permissions, routes, isLoading }, dispatch] = useReducer(reducer, initialState, () =>
13
12
  init(initialState, shouldFetchData)
14
13
  );
14
+ const fetchClient = useFetchClient();
15
15
 
16
16
  const fetchPlugins = useCallback(async () => {
17
17
  try {
@@ -21,7 +21,7 @@ const usePlugins = (shouldFetchData = true) => {
21
21
 
22
22
  const [{ permissions }, { routes }] = await Promise.all(
23
23
  [`/${pluginId}/permissions`, `/${pluginId}/routes`].map(async (endpoint) => {
24
- const res = await axiosInstance.get(endpoint);
24
+ const res = await fetchClient.get(endpoint);
25
25
 
26
26
  return res.data;
27
27
  })
@@ -46,6 +46,8 @@ const usePlugins = (shouldFetchData = true) => {
46
46
  });
47
47
  }
48
48
  }
49
+
50
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
51
  }, [toggleNotification]);
50
52
 
51
53
  useEffect(() => {
@@ -1,13 +1,17 @@
1
- import { axiosInstance, getRequestURL } from '../../../utils';
1
+ import { getFetchClient } from '@strapi/helper-plugin';
2
+ import { getRequestURL } from '../../../utils';
2
3
 
3
4
  const fetchData = async () => {
4
- const { data } = await axiosInstance.get(getRequestURL('advanced'));
5
+ const { get } = getFetchClient();
6
+ const { data } = await get(getRequestURL('advanced'));
5
7
 
6
8
  return data;
7
9
  };
8
10
 
9
11
  const putAdvancedSettings = (body) => {
10
- return axiosInstance.put(getRequestURL('advanced'), body);
12
+ const { put } = getFetchClient();
13
+
14
+ return put(getRequestURL('advanced'), body);
11
15
  };
12
16
 
13
17
  export { fetchData, putAdvancedSettings };
@@ -1,13 +1,17 @@
1
- import { axiosInstance, getRequestURL } from '../../../utils';
1
+ import { getFetchClient } from '@strapi/helper-plugin';
2
+ import { getRequestURL } from '../../../utils';
2
3
 
3
4
  const fetchData = async () => {
4
- const { data } = await axiosInstance.get(getRequestURL('email-templates'));
5
+ const { get } = getFetchClient();
6
+ const { data } = await get(getRequestURL('email-templates'));
5
7
 
6
8
  return data;
7
9
  };
8
10
 
9
11
  const putEmailTemplate = (body) => {
10
- return axiosInstance.put(getRequestURL('email-templates'), body);
12
+ const { put } = getFetchClient();
13
+
14
+ return put(getRequestURL('email-templates'), body);
11
15
  };
12
16
 
13
17
  export { fetchData, putEmailTemplate };
@@ -1,9 +1,11 @@
1
- import { getRequestURL, axiosInstance } from '../../../utils';
1
+ import { getFetchClient } from '@strapi/helper-plugin';
2
+ import { getRequestURL } from '../../../utils';
2
3
 
3
4
  // eslint-disable-next-line import/prefer-default-export
4
5
  export const fetchData = async (toggleNotification) => {
5
6
  try {
6
- const { data } = await axiosInstance.get(getRequestURL('providers'));
7
+ const { get } = getFetchClient();
8
+ const { data } = await get(getRequestURL('providers'));
7
9
 
8
10
  return data;
9
11
  } catch (err) {
@@ -17,5 +19,7 @@ export const fetchData = async (toggleNotification) => {
17
19
  };
18
20
 
19
21
  export const putProvider = (body) => {
20
- return axiosInstance.put(getRequestURL('providers'), body);
22
+ const { put } = getFetchClient();
23
+
24
+ return put(getRequestURL('providers'), body);
21
25
  };
@@ -173,6 +173,21 @@ const forms = {
173
173
  },
174
174
  },
175
175
  ],
176
+ [
177
+ {
178
+ intlLabel: {
179
+ id: getTrad({ id: 'PopUpForm.Providers.jwksurl.label' }),
180
+ defaultMessage: 'JWKS URL',
181
+ },
182
+ name: 'jwksurl',
183
+ type: 'text',
184
+ placeholder: textPlaceholder,
185
+ size: 12,
186
+ validations: {
187
+ required: false,
188
+ },
189
+ },
190
+ ],
176
191
 
177
192
  [
178
193
  {
@@ -15,6 +15,7 @@ import { useIntl } from 'react-intl';
15
15
  import {
16
16
  useOverlayBlocker,
17
17
  SettingsPageTitle,
18
+ useFetchClient,
18
19
  useTracking,
19
20
  Form,
20
21
  useNotification,
@@ -24,7 +25,6 @@ import getTrad from '../../../utils/getTrad';
24
25
  import pluginId from '../../../pluginId';
25
26
  import { usePlugins } from '../../../hooks';
26
27
  import schema from './utils/schema';
27
- import axiosInstance from '../../../utils/axiosInstance';
28
28
 
29
29
  const EditPage = () => {
30
30
  const { formatMessage } = useIntl();
@@ -35,6 +35,7 @@ const EditPage = () => {
35
35
  const { isLoading: isLoadingPlugins, permissions, routes } = usePlugins();
36
36
  const { trackUsage } = useTracking();
37
37
  const permissionsRef = useRef();
38
+ const { post } = useFetchClient();
38
39
 
39
40
  const handleCreateRoleSubmit = async (data) => {
40
41
  // Set loading state
@@ -43,7 +44,7 @@ const EditPage = () => {
43
44
  try {
44
45
  const permissions = permissionsRef.current.getPermissions();
45
46
  // Update role in Strapi
46
- await axiosInstance.post(`/${pluginId}/roles`, { ...data, ...permissions, users: [] });
47
+ await post(`/${pluginId}/roles`, { ...data, ...permissions, users: [] });
47
48
  // Notify success
48
49
  trackUsage('didCreateRole');
49
50
  toggleNotification({
@@ -3,6 +3,7 @@ import { Formik } from 'formik';
3
3
  import { useIntl } from 'react-intl';
4
4
  import { useRouteMatch } from 'react-router-dom';
5
5
  import {
6
+ useFetchClient,
6
7
  useOverlayBlocker,
7
8
  SettingsPageTitle,
8
9
  LoadingIndicatorPage,
@@ -26,7 +27,6 @@ import getTrad from '../../../utils/getTrad';
26
27
  import pluginId from '../../../pluginId';
27
28
  import { usePlugins, useFetchRole } from '../../../hooks';
28
29
  import schema from './utils/schema';
29
- import axiosInstance from '../../../utils/axiosInstance';
30
30
 
31
31
  const EditPage = () => {
32
32
  const { formatMessage } = useIntl();
@@ -39,6 +39,7 @@ const EditPage = () => {
39
39
  const { isLoading: isLoadingPlugins, routes } = usePlugins();
40
40
  const { role, onSubmitSucceeded, isLoading: isLoadingRole } = useFetchRole(id);
41
41
  const permissionsRef = useRef();
42
+ const { put } = useFetchClient();
42
43
 
43
44
  const handleEditRoleSubmit = async (data) => {
44
45
  // Set loading state
@@ -47,7 +48,7 @@ const EditPage = () => {
47
48
  try {
48
49
  const permissions = permissionsRef.current.getPermissions();
49
50
  // Update role in Strapi
50
- await axiosInstance.put(`/${pluginId}/roles/${id}`, { ...data, ...permissions, users: [] });
51
+ await put(`/${pluginId}/roles/${id}`, { ...data, ...permissions, users: [] });
51
52
  // Notify success
52
53
  onSubmitSucceeded({ name: data.name, description: data.description });
53
54
  toggleNotification({
@@ -1,8 +1,10 @@
1
- import { getRequestURL, axiosInstance } from '../../../../utils';
1
+ import { getFetchClient } from '@strapi/helper-plugin';
2
+ import { getRequestURL } from '../../../../utils';
2
3
 
3
4
  export const fetchData = async (toggleNotification, notifyStatus) => {
4
5
  try {
5
- const { data } = await axiosInstance.get(getRequestURL('roles'));
6
+ const { get } = getFetchClient();
7
+ const { data } = await get(getRequestURL('roles'));
6
8
  notifyStatus('The roles have loaded successfully');
7
9
 
8
10
  return data;
@@ -18,7 +20,8 @@ export const fetchData = async (toggleNotification, notifyStatus) => {
18
20
 
19
21
  export const deleteData = async (id, toggleNotification) => {
20
22
  try {
21
- await axiosInstance.delete(`${getRequestURL('roles')}/${id}`);
23
+ const { del } = getFetchClient();
24
+ await del(`${getRequestURL('roles')}/${id}`);
22
25
  } catch (error) {
23
26
  toggleNotification({
24
27
  type: 'warning',
@@ -1,4 +1,3 @@
1
- export { default as axiosInstance } from './axiosInstance';
2
1
  export { default as cleanPermissions } from './cleanPermissions';
3
2
  export { default as getRequestURL } from './getRequestURL';
4
3
  export { default as getTrad } from './getTrad';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/plugin-users-permissions",
3
- "version": "4.6.0-beta.2",
3
+ "version": "4.6.1",
4
4
  "description": "Protect your API with a full-authentication process based on JWT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,18 +27,19 @@
27
27
  "test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
28
28
  },
29
29
  "dependencies": {
30
- "@strapi/helper-plugin": "4.6.0-beta.2",
31
- "@strapi/utils": "4.6.0-beta.2",
30
+ "@strapi/helper-plugin": "4.6.1",
31
+ "@strapi/utils": "4.6.1",
32
32
  "bcryptjs": "2.4.3",
33
33
  "grant-koa": "5.4.8",
34
34
  "jsonwebtoken": "9.0.0",
35
+ "jwk-to-pem": "2.0.5",
35
36
  "koa": "^2.13.4",
36
37
  "koa2-ratelimit": "^1.1.2",
37
38
  "lodash": "4.17.21",
38
39
  "purest": "4.0.2",
39
40
  "react": "^17.0.2",
40
41
  "react-dom": "^17.0.2",
41
- "react-intl": "5.25.1",
42
+ "react-intl": "6.2.7",
42
43
  "react-redux": "7.2.8",
43
44
  "react-router": "^5.2.0",
44
45
  "react-router-dom": "5.3.4",
@@ -64,5 +65,5 @@
64
65
  "required": true,
65
66
  "kind": "plugin"
66
67
  },
67
- "gitHead": "b852090f931cd21868c4016f24db2f9fdfc7a7ab"
68
+ "gitHead": "17a7845e3d453ea2e7911bda6ec25ed196dd5f16"
68
69
  }
@@ -120,4 +120,12 @@ module.exports = (baseURL) => ({
120
120
  scope: ['openid email'], // scopes should be space delimited
121
121
  subdomain: 'my.subdomain.com/cas',
122
122
  },
123
+ patreon: {
124
+ enabled: false,
125
+ icon: '',
126
+ key: '',
127
+ secret: '',
128
+ callback: `${baseURL}/patreon/callback`,
129
+ scope: ['identity', 'identity[email]'],
130
+ },
123
131
  });
@@ -2,10 +2,53 @@
2
2
 
3
3
  const { strict: assert } = require('assert');
4
4
  const jwt = require('jsonwebtoken');
5
+ const jwkToPem = require('jwk-to-pem');
6
+
7
+ const getCognitoPayload = async ({ idToken, jwksUrl, purest }) => {
8
+ const {
9
+ header: { kid },
10
+ payload,
11
+ } = jwt.decode(idToken, { complete: true });
12
+
13
+ if (!payload || !kid) {
14
+ throw new Error('The provided token is not valid');
15
+ }
16
+
17
+ const config = {
18
+ cognito: {
19
+ discovery: {
20
+ origin: jwksUrl.origin,
21
+ path: jwksUrl.pathname,
22
+ },
23
+ },
24
+ };
25
+ try {
26
+ const cognito = purest({ provider: 'cognito', config });
27
+ // get the JSON Web Key (JWK) for the user pool
28
+ const { body: jwk } = await cognito('discovery').request();
29
+ // Get the key with the same Key ID as the provided token
30
+ const key = jwk.keys.find(({ kid: jwkKid }) => jwkKid === kid);
31
+ const pem = jwkToPem(key);
32
+
33
+ // https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
34
+ const decodedToken = await new Promise((resolve, reject) => {
35
+ jwt.verify(idToken, pem, { algorithms: ['RS256'] }, (err, decodedToken) => {
36
+ if (err) {
37
+ reject();
38
+ }
39
+ resolve(decodedToken);
40
+ });
41
+ });
42
+ return decodedToken;
43
+ } catch (err) {
44
+ throw new Error('There was an error verifying the token');
45
+ }
46
+ };
5
47
 
6
48
  const getInitialProviders = ({ purest }) => ({
7
49
  async discord({ accessToken }) {
8
50
  const discord = purest({ provider: 'discord' });
51
+
9
52
  return discord
10
53
  .get('users/@me')
11
54
  .auth(accessToken)
@@ -19,19 +62,14 @@ const getInitialProviders = ({ purest }) => ({
19
62
  };
20
63
  });
21
64
  },
22
- async cognito({ query }) {
23
- // get the id_token
65
+ async cognito({ query, providers }) {
66
+ const jwksUrl = new URL(providers.cognito.jwksurl);
24
67
  const idToken = query.id_token;
25
- // decode the jwt token
26
- const tokenPayload = jwt.decode(idToken);
27
- if (!tokenPayload) {
28
- throw new Error('unable to decode jwt token');
29
- } else {
30
- return {
31
- username: tokenPayload['cognito:username'],
32
- email: tokenPayload.email,
33
- };
34
- }
68
+ const tokenPayload = await getCognitoPayload({ idToken, jwksUrl, purest });
69
+ return {
70
+ username: tokenPayload['cognito:username'],
71
+ email: tokenPayload.email,
72
+ };
35
73
  },
36
74
  async facebook({ accessToken }) {
37
75
  const facebook = purest({ provider: 'facebook' });
@@ -264,6 +302,35 @@ const getInitialProviders = ({ purest }) => ({
264
302
  };
265
303
  });
266
304
  },
305
+ async patreon({ accessToken }) {
306
+ const patreon = purest({
307
+ provider: 'patreon',
308
+ config: {
309
+ patreon: {
310
+ default: {
311
+ origin: 'https://www.patreon.com',
312
+ path: 'api/oauth2/{path}',
313
+ headers: {
314
+ authorization: 'Bearer {auth}',
315
+ },
316
+ },
317
+ },
318
+ },
319
+ });
320
+
321
+ return patreon
322
+ .get('v2/identity')
323
+ .auth(accessToken)
324
+ .qs(new URLSearchParams({ 'fields[user]': 'full_name,email' }).toString())
325
+ .request()
326
+ .then(({ body }) => {
327
+ const patreonData = body.data.attributes;
328
+ return {
329
+ username: patreonData.full_name,
330
+ email: patreonData.email,
331
+ };
332
+ });
333
+ },
267
334
  });
268
335
 
269
336
  module.exports = () => {
@@ -78,7 +78,7 @@ module.exports = ({ strapi }) => {
78
78
  return user;
79
79
  }
80
80
 
81
- if (users.length > 1 && advancedSettings.unique_email) {
81
+ if (users.length && advancedSettings.unique_email) {
82
82
  throw new Error('Email is already taken.');
83
83
  }
84
84
 
@@ -1,38 +0,0 @@
1
- import axios from 'axios';
2
- import { auth, wrapAxiosInstance } from '@strapi/helper-plugin';
3
-
4
- const instance = axios.create({
5
- baseURL: process.env.STRAPI_ADMIN_BACKEND_URL,
6
- });
7
-
8
- instance.interceptors.request.use(
9
- async (config) => {
10
- config.headers = {
11
- Authorization: `Bearer ${auth.getToken()}`,
12
- Accept: 'application/json',
13
- 'Content-Type': 'application/json',
14
- };
15
-
16
- return config;
17
- },
18
- (error) => {
19
- Promise.reject(error);
20
- }
21
- );
22
-
23
- instance.interceptors.response.use(
24
- (response) => response,
25
- (error) => {
26
- // whatever you want to do with the error
27
- if (error.response?.status === 401) {
28
- auth.clearAppStorage();
29
- window.location.reload();
30
- }
31
-
32
- throw error;
33
- }
34
- );
35
-
36
- const wrapper = wrapAxiosInstance(instance);
37
-
38
- export default wrapper;