@plone/volto 14.9.0 → 15.0.0-alpha.2
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/CHANGELOG.md +33 -0
- package/package.json +3 -2
- package/src/actions/language/language.js +6 -4
- package/src/components/manage/Preferences/PersonalPreferences.jsx +4 -2
- package/src/components/manage/Sidebar/Sidebar.jsx +6 -3
- package/src/components/manage/Toolbar/PersonalTools.jsx +2 -0
- package/src/components/manage/Toolbar/PersonalTools.test.jsx +11 -8
- package/src/components/manage/Toolbar/Toolbar.jsx +17 -11
- package/src/components/manage/Widgets/SelectAutoComplete.jsx +2 -2
- package/src/components/theme/LanguageSelector/LanguageSelector.js +2 -18
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +20 -5
- package/src/helpers/Api/APIResourceWithAuth.js +1 -2
- package/src/helpers/Api/Api.js +11 -4
- package/src/helpers/AuthToken/AuthToken.js +15 -7
- package/src/helpers/AuthToken/AuthToken.test.js +19 -12
- package/src/helpers/Robots/Robots.js +1 -2
- package/src/helpers/Sitemap/Sitemap.js +2 -3
- package/src/middleware/api.js +3 -2
- package/src/server.jsx +13 -13
- package/src/start-client.jsx +12 -9
- package/pyvenv.cfg +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 15.0.0-alpha.2 (2022-02-10)
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Language Switcher no longer takes care of the change of the language on the Redux Store. This responsability has been unified in the `MultilingualRedirector` @sneridagh
|
|
8
|
+
|
|
9
|
+
### Bugfix
|
|
10
|
+
|
|
11
|
+
- Prevent the MultilingualRedirector to force 4 content load when switching the language @reebalazs
|
|
12
|
+
|
|
13
|
+
### Documentation
|
|
14
|
+
|
|
15
|
+
- Upgrade Guide i18n: Make clear what's project, what add-on. @ksuess
|
|
16
|
+
|
|
17
|
+
## 15.0.0-alpha.1 (2022-02-09)
|
|
18
|
+
|
|
19
|
+
### Bugfix
|
|
20
|
+
|
|
21
|
+
- Fix the `null` error in SelectAutoComplete Widget @iFlameing
|
|
22
|
+
|
|
23
|
+
## 15.0.0-alpha.0 (2022-02-09)
|
|
24
|
+
|
|
25
|
+
### Breaking
|
|
26
|
+
|
|
27
|
+
- Upgrade `react-cookie` to latest version. @sneridagh @robgietema
|
|
28
|
+
See https://docs.voltocms.com/upgrade-guide/ for more information.
|
|
29
|
+
|
|
30
|
+
## 14.10.0 (2022-02-08)
|
|
31
|
+
|
|
32
|
+
### Feature
|
|
33
|
+
|
|
34
|
+
- Add Pluggable to toolbar user menu. @ksuess
|
|
35
|
+
|
|
3
36
|
## 14.9.0 (2022-02-08)
|
|
4
37
|
|
|
5
38
|
### Feature
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
}
|
|
10
10
|
],
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"version": "
|
|
12
|
+
"version": "15.0.0-alpha.2",
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
15
|
"url": "git@github.com:plone/volto.git"
|
|
@@ -350,7 +350,7 @@
|
|
|
350
350
|
"react-anchor-link-smooth-scroll": "1.0.12",
|
|
351
351
|
"react-animate-height": "2.0.17",
|
|
352
352
|
"react-beautiful-dnd": "13.0.0",
|
|
353
|
-
"react-cookie": "1.
|
|
353
|
+
"react-cookie": "4.1.1",
|
|
354
354
|
"react-dates": "21.5.1",
|
|
355
355
|
"react-detect-click-outside": "1.1.1",
|
|
356
356
|
"react-dnd": "5.0.0",
|
|
@@ -401,6 +401,7 @@
|
|
|
401
401
|
"svgo-loader": "2.2.1",
|
|
402
402
|
"tlds": "1.203.1",
|
|
403
403
|
"undoo": "0.5.0",
|
|
404
|
+
"universal-cookie-express": "4.0.3",
|
|
404
405
|
"use-deep-compare-effect": "1.6.1",
|
|
405
406
|
"xmlrpc": "1.3.2",
|
|
406
407
|
"yarn-deduplicate": "3.1.0",
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import cookie from 'react-cookie';
|
|
2
1
|
import { updateIntl } from 'react-intl-redux';
|
|
3
2
|
import { normalizeLanguageName } from '@plone/volto/helpers';
|
|
3
|
+
import Cookies from 'universal-cookie';
|
|
4
4
|
|
|
5
5
|
export function changeLanguageCookies(language) {
|
|
6
|
-
|
|
6
|
+
const cookies = new Cookies();
|
|
7
|
+
|
|
8
|
+
cookies.set('lang', normalizeLanguageName(language), {
|
|
7
9
|
expires: new Date((2 ** 31 - 1) * 1000),
|
|
8
10
|
path: '/',
|
|
9
11
|
});
|
|
10
|
-
|
|
12
|
+
cookies.set('I18N_LANGUAGE', normalizeLanguageName(language) || '', {
|
|
11
13
|
expires: new Date((2 ** 31 - 1) * 1000),
|
|
12
14
|
path: '/',
|
|
13
15
|
});
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
|
-
* Changes current language using react-intl-redux action and setting the cookie
|
|
19
|
+
* Changes current language using react-intl-redux action and setting the cookie
|
|
18
20
|
* @function changeLanguage
|
|
19
21
|
* @param {string} language target language.
|
|
20
22
|
* @param {number} locale set of locales corresponding the target language.
|
|
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
|
|
|
8
8
|
import { connect } from 'react-redux';
|
|
9
9
|
import { compose } from 'redux';
|
|
10
10
|
import { map, keys } from 'lodash';
|
|
11
|
-
import
|
|
11
|
+
import { withCookies } from 'react-cookie';
|
|
12
12
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
13
13
|
import { toast } from 'react-toastify';
|
|
14
14
|
|
|
@@ -112,9 +112,10 @@ class PersonalPreferences extends Component {
|
|
|
112
112
|
* @returns {string} Markup for the component.
|
|
113
113
|
*/
|
|
114
114
|
render() {
|
|
115
|
+
const { cookies } = this.props;
|
|
115
116
|
return (
|
|
116
117
|
<Form
|
|
117
|
-
formData={{ language:
|
|
118
|
+
formData={{ language: cookies.get('I18N_LANGUAGE') || '' }}
|
|
118
119
|
schema={{
|
|
119
120
|
fieldsets: [
|
|
120
121
|
{
|
|
@@ -144,5 +145,6 @@ class PersonalPreferences extends Component {
|
|
|
144
145
|
|
|
145
146
|
export default compose(
|
|
146
147
|
injectIntl,
|
|
148
|
+
withCookies,
|
|
147
149
|
connect(null, { changeLanguage }),
|
|
148
150
|
)(PersonalPreferences);
|
|
@@ -8,7 +8,7 @@ import PropTypes from 'prop-types';
|
|
|
8
8
|
import { Button, Tab } from 'semantic-ui-react';
|
|
9
9
|
import { connect } from 'react-redux';
|
|
10
10
|
import { compose } from 'redux';
|
|
11
|
-
import
|
|
11
|
+
import { withCookies } from 'react-cookie';
|
|
12
12
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
13
13
|
import cx from 'classnames';
|
|
14
14
|
import { BodyClass } from '@plone/volto/helpers';
|
|
@@ -77,11 +77,12 @@ class Sidebar extends Component {
|
|
|
77
77
|
*/
|
|
78
78
|
constructor(props) {
|
|
79
79
|
super(props);
|
|
80
|
+
const { cookies } = props;
|
|
80
81
|
this.onToggleExpanded = this.onToggleExpanded.bind(this);
|
|
81
82
|
this.onToggleFullSize = this.onToggleFullSize.bind(this);
|
|
82
83
|
this.onTabChange = this.onTabChange.bind(this);
|
|
83
84
|
this.state = {
|
|
84
|
-
expanded:
|
|
85
|
+
expanded: cookies.get('sidebar_expanded') !== 'false',
|
|
85
86
|
size: 0,
|
|
86
87
|
showFull: true,
|
|
87
88
|
showFullToolbarExpanded: true,
|
|
@@ -95,7 +96,8 @@ class Sidebar extends Component {
|
|
|
95
96
|
* @returns {undefined}
|
|
96
97
|
*/
|
|
97
98
|
onToggleExpanded() {
|
|
98
|
-
|
|
99
|
+
const { cookies } = this.props;
|
|
100
|
+
cookies.set('sidebar_expanded', !this.state.expanded, {
|
|
99
101
|
expires: new Date((2 ** 31 - 1) * 1000),
|
|
100
102
|
path: '/',
|
|
101
103
|
});
|
|
@@ -269,6 +271,7 @@ class Sidebar extends Component {
|
|
|
269
271
|
|
|
270
272
|
export default compose(
|
|
271
273
|
injectIntl,
|
|
274
|
+
withCookies,
|
|
272
275
|
connect(
|
|
273
276
|
(state) => ({
|
|
274
277
|
tab: state.sidebar.tab,
|
|
@@ -11,6 +11,7 @@ import cx from 'classnames';
|
|
|
11
11
|
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
|
12
12
|
import { Icon } from '@plone/volto/components';
|
|
13
13
|
import { getUser } from '@plone/volto/actions';
|
|
14
|
+
import { Pluggable } from '@plone/volto/components/manage/Pluggable';
|
|
14
15
|
import { userHasRoles } from '@plone/volto/helpers';
|
|
15
16
|
|
|
16
17
|
import logoutSVG from '@plone/volto/icons/log-out.svg';
|
|
@@ -172,6 +173,7 @@ class PersonalTools extends Component {
|
|
|
172
173
|
</Link>
|
|
173
174
|
</li>
|
|
174
175
|
)}
|
|
176
|
+
<Pluggable name="toolbar-user-menu" />
|
|
175
177
|
</ul>
|
|
176
178
|
</div>
|
|
177
179
|
</div>
|
|
@@ -4,6 +4,7 @@ import configureStore from 'redux-mock-store';
|
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
6
|
import jwt from 'jsonwebtoken';
|
|
7
|
+
import { PluggablesProvider } from '@plone/volto/components/manage/Pluggable';
|
|
7
8
|
|
|
8
9
|
import PersonalTools from './PersonalTools';
|
|
9
10
|
|
|
@@ -35,14 +36,16 @@ describe('Toolbar Personal Tools component', () => {
|
|
|
35
36
|
});
|
|
36
37
|
const component = renderer.create(
|
|
37
38
|
<Provider store={store}>
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
<PluggablesProvider>
|
|
40
|
+
<MemoryRouter>
|
|
41
|
+
<PersonalTools
|
|
42
|
+
loadComponent={() => {}}
|
|
43
|
+
theToolbar={{
|
|
44
|
+
current: { getBoundingClientRect: () => ({ width: '320' }) },
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
</MemoryRouter>
|
|
48
|
+
</PluggablesProvider>
|
|
46
49
|
</Provider>,
|
|
47
50
|
);
|
|
48
51
|
const json = component.toJSON();
|
|
@@ -11,7 +11,7 @@ import jwtDecode from 'jwt-decode';
|
|
|
11
11
|
import { connect } from 'react-redux';
|
|
12
12
|
import { compose } from 'redux';
|
|
13
13
|
import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib';
|
|
14
|
-
import
|
|
14
|
+
import { withCookies } from 'react-cookie';
|
|
15
15
|
import { filter, find } from 'lodash';
|
|
16
16
|
import cx from 'classnames';
|
|
17
17
|
import config from '@plone/volto/registry';
|
|
@@ -175,17 +175,21 @@ class Toolbar extends Component {
|
|
|
175
175
|
types: [],
|
|
176
176
|
};
|
|
177
177
|
|
|
178
|
-
state = {
|
|
179
|
-
expanded: cookie.load('toolbar_expanded') !== 'false',
|
|
180
|
-
showMenu: false,
|
|
181
|
-
menuStyle: {},
|
|
182
|
-
menuComponents: [],
|
|
183
|
-
loadedComponents: [],
|
|
184
|
-
hideToolbarBody: false,
|
|
185
|
-
};
|
|
186
|
-
|
|
187
178
|
toolbarWindow = React.createRef();
|
|
188
179
|
|
|
180
|
+
constructor(props) {
|
|
181
|
+
super(props);
|
|
182
|
+
const { cookies } = props;
|
|
183
|
+
this.state = {
|
|
184
|
+
expanded: cookies.get('toolbar_expanded') !== 'false',
|
|
185
|
+
showMenu: false,
|
|
186
|
+
menuStyle: {},
|
|
187
|
+
menuComponents: [],
|
|
188
|
+
loadedComponents: [],
|
|
189
|
+
hideToolbarBody: false,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
189
193
|
/**
|
|
190
194
|
* Component will mount
|
|
191
195
|
* @method componentDidMount
|
|
@@ -226,7 +230,8 @@ class Toolbar extends Component {
|
|
|
226
230
|
}
|
|
227
231
|
|
|
228
232
|
handleShrink = () => {
|
|
229
|
-
|
|
233
|
+
const { cookies } = this.props;
|
|
234
|
+
cookies.set('toolbar_expanded', !this.state.expanded, {
|
|
230
235
|
expires: new Date((2 ** 31 - 1) * 1000),
|
|
231
236
|
path: '/',
|
|
232
237
|
});
|
|
@@ -569,6 +574,7 @@ class Toolbar extends Component {
|
|
|
569
574
|
|
|
570
575
|
export default compose(
|
|
571
576
|
injectIntl,
|
|
577
|
+
withCookies,
|
|
572
578
|
connect(
|
|
573
579
|
(state, props) => ({
|
|
574
580
|
actions: state.actions.actions,
|
|
@@ -135,11 +135,11 @@ class SelectAutoComplete extends Component {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
componentDidUpdate(prevProps, prevState) {
|
|
138
|
-
const { value, choices
|
|
138
|
+
const { value, choices } = this.props;
|
|
139
139
|
if (
|
|
140
140
|
this.state.termsPairsCache.length === 0 &&
|
|
141
141
|
value?.length > 0 &&
|
|
142
|
-
choices
|
|
142
|
+
choices?.length > 0
|
|
143
143
|
) {
|
|
144
144
|
this.setState((state) => ({
|
|
145
145
|
termsPairsCache: [...state.termsPairsCache, ...choices],
|
|
@@ -7,21 +7,14 @@ import React from 'react';
|
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import { Link } from 'react-router-dom';
|
|
9
9
|
|
|
10
|
-
import { useSelector
|
|
10
|
+
import { useSelector } from 'react-redux';
|
|
11
11
|
import cx from 'classnames';
|
|
12
12
|
import { find, map } from 'lodash';
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
Helmet,
|
|
16
|
-
langmap,
|
|
17
|
-
flattenToAppURL,
|
|
18
|
-
normalizeLanguageName,
|
|
19
|
-
} from '@plone/volto/helpers';
|
|
14
|
+
import { Helmet, langmap, flattenToAppURL } from '@plone/volto/helpers';
|
|
20
15
|
|
|
21
16
|
import config from '@plone/volto/registry';
|
|
22
17
|
|
|
23
|
-
import { changeLanguage } from '@plone/volto/actions';
|
|
24
|
-
|
|
25
18
|
import { defineMessages, useIntl } from 'react-intl';
|
|
26
19
|
|
|
27
20
|
const messages = defineMessages({
|
|
@@ -33,7 +26,6 @@ const messages = defineMessages({
|
|
|
33
26
|
|
|
34
27
|
const LanguageSelector = (props) => {
|
|
35
28
|
const intl = useIntl();
|
|
36
|
-
const dispatch = useDispatch();
|
|
37
29
|
const currentLang = useSelector((state) => state.intl.locale);
|
|
38
30
|
const translations = useSelector(
|
|
39
31
|
(state) => state.content.data?.['@components']?.translations?.items,
|
|
@@ -55,14 +47,6 @@ const LanguageSelector = (props) => {
|
|
|
55
47
|
title={langmap[lang].nativeName}
|
|
56
48
|
onClick={() => {
|
|
57
49
|
props.onClickAction();
|
|
58
|
-
if (config.settings.supportedLanguages.includes(lang)) {
|
|
59
|
-
const langFileName = normalizeLanguageName(lang);
|
|
60
|
-
import('~/../locales/' + langFileName + '.json').then(
|
|
61
|
-
(locale) => {
|
|
62
|
-
dispatch(changeLanguage(lang, locale.default));
|
|
63
|
-
},
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
50
|
}}
|
|
67
51
|
key={`language-selector-${lang}`}
|
|
68
52
|
>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Redirect } from 'react-router-dom';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
|
-
import
|
|
4
|
+
import { useCookies } from 'react-cookie';
|
|
5
5
|
import config from '@plone/volto/registry';
|
|
6
6
|
import { changeLanguage } from '@plone/volto/actions';
|
|
7
7
|
import { normalizeLanguageName } from '@plone/volto/helpers';
|
|
@@ -9,8 +9,8 @@ import { normalizeLanguageName } from '@plone/volto/helpers';
|
|
|
9
9
|
const MultilingualRedirector = (props) => {
|
|
10
10
|
const { settings } = config;
|
|
11
11
|
const { pathname, contentLanguage, children } = props;
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const [cookies] = useCookies(['name']);
|
|
13
|
+
const currentLanguage = cookies['I18N_LANGUAGE'] || settings.defaultLanguage;
|
|
14
14
|
const redirectToLanguage = settings.supportedLanguages.includes(
|
|
15
15
|
currentLanguage,
|
|
16
16
|
)
|
|
@@ -23,10 +23,14 @@ const MultilingualRedirector = (props) => {
|
|
|
23
23
|
// UI are the same. Otherwise, there are inconsistencies between the UI main elements
|
|
24
24
|
// eg. Home link in breadcrumbs, other i18n dependant literals from the main UI and
|
|
25
25
|
// the current content language.
|
|
26
|
+
// This variable makes sure that the async operation in the lazy load import does not
|
|
27
|
+
// happen twice (when switching language)
|
|
28
|
+
let mounted = true;
|
|
26
29
|
if (
|
|
27
30
|
contentLanguage &&
|
|
28
31
|
currentLanguage !== contentLanguage &&
|
|
29
32
|
pathname &&
|
|
33
|
+
pathname !== '/' &&
|
|
30
34
|
// We don't want to trigger it in Babel View, since Babel view already takes care
|
|
31
35
|
// of it
|
|
32
36
|
!pathname.endsWith('/add') &&
|
|
@@ -34,9 +38,14 @@ const MultilingualRedirector = (props) => {
|
|
|
34
38
|
) {
|
|
35
39
|
const langFileName = normalizeLanguageName(contentLanguage);
|
|
36
40
|
import('~/../locales/' + langFileName + '.json').then((locale) => {
|
|
37
|
-
|
|
41
|
+
if (mounted) {
|
|
42
|
+
dispatch(changeLanguage(contentLanguage, locale.default));
|
|
43
|
+
}
|
|
38
44
|
});
|
|
39
45
|
}
|
|
46
|
+
return () => {
|
|
47
|
+
mounted = false;
|
|
48
|
+
};
|
|
40
49
|
}, [
|
|
41
50
|
pathname,
|
|
42
51
|
dispatch,
|
|
@@ -48,12 +57,18 @@ const MultilingualRedirector = (props) => {
|
|
|
48
57
|
React.useEffect(() => {
|
|
49
58
|
// ToDo: Add means to support language negotiation (with config)
|
|
50
59
|
// const detectedLang = (navigator.language || navigator.userLanguage).substring(0, 2);
|
|
60
|
+
let mounted = true;
|
|
51
61
|
if (settings.isMultilingual && pathname === '/') {
|
|
52
62
|
const langFileName = normalizeLanguageName(redirectToLanguage);
|
|
53
63
|
import('~/../locales/' + langFileName + '.json').then((locale) => {
|
|
54
|
-
|
|
64
|
+
if (mounted) {
|
|
65
|
+
dispatch(changeLanguage(redirectToLanguage, locale.default));
|
|
66
|
+
}
|
|
55
67
|
});
|
|
56
68
|
}
|
|
69
|
+
return () => {
|
|
70
|
+
mounted = false;
|
|
71
|
+
};
|
|
57
72
|
}, [pathname, dispatch, redirectToLanguage, settings.isMultilingual]);
|
|
58
73
|
|
|
59
74
|
return pathname === '/' && settings.isMultilingual ? (
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import superagent from 'superagent';
|
|
7
|
-
import cookie from 'react-cookie';
|
|
8
7
|
import config from '@plone/volto/registry';
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -30,7 +29,7 @@ export const getAPIResourceWithAuth = (req) =>
|
|
|
30
29
|
.get(`${apiPath}${APISUFIX}${req.path}`)
|
|
31
30
|
.maxResponseSize(settings.maxResponseSize)
|
|
32
31
|
.responseType('blob');
|
|
33
|
-
const authToken =
|
|
32
|
+
const authToken = req.universalCookies.get('auth_token');
|
|
34
33
|
if (authToken) {
|
|
35
34
|
request.set('Authorization', `Bearer ${authToken}`);
|
|
36
35
|
}
|
package/src/helpers/Api/Api.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import superagent from 'superagent';
|
|
7
|
-
import
|
|
7
|
+
import Cookies from 'universal-cookie';
|
|
8
8
|
import config from '@plone/volto/registry';
|
|
9
9
|
|
|
10
10
|
const methods = ['get', 'post', 'put', 'patch', 'del'];
|
|
@@ -42,7 +42,9 @@ class Api {
|
|
|
42
42
|
* @method constructor
|
|
43
43
|
* @constructs Api
|
|
44
44
|
*/
|
|
45
|
-
constructor() {
|
|
45
|
+
constructor(req) {
|
|
46
|
+
const cookies = new Cookies();
|
|
47
|
+
|
|
46
48
|
methods.forEach((method) => {
|
|
47
49
|
this[method] = (path, { params, data, type, headers = {} } = {}) => {
|
|
48
50
|
let request;
|
|
@@ -53,10 +55,15 @@ class Api {
|
|
|
53
55
|
request.query(params);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
let authToken;
|
|
59
|
+
if (req) {
|
|
60
|
+
// We are in SSR
|
|
61
|
+
authToken = req.universalCookies.get('auth_token');
|
|
62
|
+
} else {
|
|
63
|
+
authToken = cookies.get('auth_token');
|
|
64
|
+
}
|
|
57
65
|
if (authToken) {
|
|
58
66
|
request.set('Authorization', `Bearer ${authToken}`);
|
|
59
|
-
if (__SERVER__) request.set('Cookie', `auth_token=${authToken}`);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
request.set('Accept', 'application/json');
|
|
@@ -3,19 +3,20 @@
|
|
|
3
3
|
* @module helpers/AuthToken
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import Cookies from 'universal-cookie';
|
|
7
7
|
import jwtDecode from 'jwt-decode';
|
|
8
8
|
|
|
9
9
|
import { loginRenew } from '@plone/volto/actions';
|
|
10
10
|
import { push } from 'connected-react-router';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Get auth token method
|
|
13
|
+
* Get auth token method (does not work in SSR)
|
|
14
14
|
* @method getAuthToken
|
|
15
15
|
* @returns {undefined}
|
|
16
16
|
*/
|
|
17
17
|
export function getAuthToken() {
|
|
18
|
-
|
|
18
|
+
const cookies = new Cookies();
|
|
19
|
+
return cookies.get('auth_token');
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -24,8 +25,15 @@ export function getAuthToken() {
|
|
|
24
25
|
* @param {object} store Redux store.
|
|
25
26
|
* @returns {undefined}
|
|
26
27
|
*/
|
|
27
|
-
export function persistAuthToken(store) {
|
|
28
|
-
|
|
28
|
+
export function persistAuthToken(store, req) {
|
|
29
|
+
const cookies = new Cookies();
|
|
30
|
+
let currentValue;
|
|
31
|
+
if (req) {
|
|
32
|
+
// We are in SSR
|
|
33
|
+
currentValue = req.universalCookies.get('auth_token');
|
|
34
|
+
} else {
|
|
35
|
+
currentValue = cookies.get('auth_token');
|
|
36
|
+
}
|
|
29
37
|
|
|
30
38
|
/**
|
|
31
39
|
* handleChange method.
|
|
@@ -50,11 +58,11 @@ export function persistAuthToken(store) {
|
|
|
50
58
|
if (previousValue !== currentValue || initial) {
|
|
51
59
|
if (!currentValue) {
|
|
52
60
|
if (previousValue) {
|
|
53
|
-
|
|
61
|
+
cookies.remove('auth_token', { path: '/' });
|
|
54
62
|
}
|
|
55
63
|
} else {
|
|
56
64
|
if (previousValue !== currentValue) {
|
|
57
|
-
|
|
65
|
+
cookies.set('auth_token', currentValue, {
|
|
58
66
|
path: '/',
|
|
59
67
|
expires: new Date(jwtDecode(currentValue).exp * 1000),
|
|
60
68
|
});
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Cookies from 'universal-cookie';
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
3
|
import jwtDecode from 'jwt-decode';
|
|
4
4
|
|
|
5
5
|
import { getAuthToken, persistAuthToken } from './AuthToken';
|
|
6
6
|
|
|
7
|
-
jest.mock('
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
jest.mock('universal-cookie', () => {
|
|
8
|
+
const mCookie = {
|
|
9
|
+
get: jest
|
|
10
|
+
.fn(() => require('jsonwebtoken').sign({ exp: 1 }, 'secret')) // eslint-disable-line global-require
|
|
11
|
+
.mockImplementationOnce(() => null), // the first call is for anonymous, no auth_token cookie
|
|
12
|
+
remove: jest.fn(),
|
|
13
|
+
set: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
return jest.fn(() => mCookie);
|
|
16
|
+
});
|
|
14
17
|
|
|
15
18
|
describe('AuthToken', () => {
|
|
16
19
|
describe('anonymousAuthToken', () => {
|
|
17
20
|
it('avoid unnecessary removing auth token', () => {
|
|
21
|
+
const cookies = new Cookies();
|
|
18
22
|
const store = {
|
|
19
23
|
subscribe: jest.fn(),
|
|
20
24
|
getState: jest.fn(() => ({
|
|
@@ -24,19 +28,21 @@ describe('AuthToken', () => {
|
|
|
24
28
|
})),
|
|
25
29
|
};
|
|
26
30
|
persistAuthToken(store);
|
|
27
|
-
expect(
|
|
31
|
+
expect(cookies.remove).not.toBeCalledWith('auth_token', { path: '/' });
|
|
28
32
|
});
|
|
29
33
|
});
|
|
30
34
|
|
|
31
35
|
describe('getAuthToken', () => {
|
|
32
36
|
it('can get the auth token', () => {
|
|
37
|
+
const cookies = new Cookies();
|
|
33
38
|
getAuthToken();
|
|
34
|
-
expect(
|
|
39
|
+
expect(cookies.get).toBeCalledWith('auth_token');
|
|
35
40
|
});
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
describe('persistAuthToken', () => {
|
|
39
44
|
it('can set a new auth token', () => {
|
|
45
|
+
const cookies = new Cookies();
|
|
40
46
|
const store = {
|
|
41
47
|
subscribe: jest.fn(),
|
|
42
48
|
getState: jest.fn(() => ({
|
|
@@ -48,13 +54,14 @@ describe('AuthToken', () => {
|
|
|
48
54
|
const { token } = store.getState().userSession;
|
|
49
55
|
|
|
50
56
|
persistAuthToken(store);
|
|
51
|
-
expect(
|
|
57
|
+
expect(cookies.set).toBeCalledWith('auth_token', token, {
|
|
52
58
|
path: '/',
|
|
53
59
|
expires: new Date(jwtDecode(token).exp * 1000),
|
|
54
60
|
});
|
|
55
61
|
});
|
|
56
62
|
|
|
57
63
|
it('can remove an auth token', () => {
|
|
64
|
+
const cookies = new Cookies();
|
|
58
65
|
const store = {
|
|
59
66
|
subscribe: jest.fn(),
|
|
60
67
|
getState: jest.fn(() => ({
|
|
@@ -65,7 +72,7 @@ describe('AuthToken', () => {
|
|
|
65
72
|
};
|
|
66
73
|
|
|
67
74
|
persistAuthToken(store);
|
|
68
|
-
expect(
|
|
75
|
+
expect(cookies.remove).toBeCalledWith('auth_token', { path: '/' });
|
|
69
76
|
});
|
|
70
77
|
});
|
|
71
78
|
});
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import superagent from 'superagent';
|
|
7
|
-
import cookie from 'react-cookie';
|
|
8
7
|
import config from '@plone/volto/registry';
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -23,7 +22,7 @@ export const generateRobots = (req) =>
|
|
|
23
22
|
);
|
|
24
23
|
request.set('Accept', 'text/plain');
|
|
25
24
|
|
|
26
|
-
const authToken =
|
|
25
|
+
const authToken = req.universalCookies.get('auth_token');
|
|
27
26
|
if (authToken) {
|
|
28
27
|
request.set('Authorization', `Bearer ${authToken}`);
|
|
29
28
|
}
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import superagent from 'superagent';
|
|
7
7
|
import { map } from 'lodash';
|
|
8
|
-
import cookie from 'react-cookie';
|
|
9
8
|
import zlib from 'zlib';
|
|
10
9
|
import { toPublicURL } from '@plone/volto/helpers';
|
|
11
10
|
|
|
@@ -24,7 +23,7 @@ export const generateSitemap = (_req) =>
|
|
|
24
23
|
`${settings.apiPath}/@search?metadata_fields=modified&b_size=100000000&use_site_search_settings=1`,
|
|
25
24
|
);
|
|
26
25
|
request.set('Accept', 'application/json');
|
|
27
|
-
const authToken =
|
|
26
|
+
const authToken = _req.universalCookies.get('auth_token');
|
|
28
27
|
if (authToken) {
|
|
29
28
|
request.set('Authorization', `Bearer ${authToken}`);
|
|
30
29
|
}
|
|
@@ -35,7 +34,7 @@ export const generateSitemap = (_req) =>
|
|
|
35
34
|
const items = map(
|
|
36
35
|
body.items,
|
|
37
36
|
(item) =>
|
|
38
|
-
` <url>\n <loc>${toPublicURL(item['@id'])}</loc>\n
|
|
37
|
+
` <url>\n <loc>${toPublicURL(item['@id'])}</loc>\n
|
|
39
38
|
<lastmod>${item.modified}</lastmod>\n </url>`,
|
|
40
39
|
);
|
|
41
40
|
const result = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">\n${items.join(
|
package/src/middleware/api.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module middleware/api
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import Cookies from 'universal-cookie';
|
|
7
7
|
import jwtDecode from 'jwt-decode';
|
|
8
8
|
import { compact, flatten, union } from 'lodash';
|
|
9
9
|
import { matchPath } from 'react-router';
|
|
@@ -174,7 +174,8 @@ export default (api) => ({ dispatch, getState }) => (next) => (action) => {
|
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
if (type === LOGIN && settings.websockets) {
|
|
177
|
-
|
|
177
|
+
const cookies = new Cookies();
|
|
178
|
+
cookies.set('auth_token', result.token, {
|
|
178
179
|
path: '/',
|
|
179
180
|
expires: new Date(jwtDecode(result.token).exp * 1000),
|
|
180
181
|
});
|
package/src/server.jsx
CHANGED
|
@@ -9,12 +9,13 @@ import { renderToString } from 'react-dom/server';
|
|
|
9
9
|
import { createMemoryHistory } from 'history';
|
|
10
10
|
import { parse as parseUrl } from 'url';
|
|
11
11
|
import { keys } from 'lodash';
|
|
12
|
-
import cookie, { plugToRequest } from 'react-cookie';
|
|
13
12
|
import locale from 'locale';
|
|
14
13
|
import { detect } from 'detect-browser';
|
|
15
14
|
import path from 'path';
|
|
16
15
|
import { ChunkExtractor, ChunkExtractorManager } from '@loadable/server';
|
|
17
16
|
import { resetServerContext } from 'react-beautiful-dnd';
|
|
17
|
+
import { CookiesProvider } from 'react-cookie';
|
|
18
|
+
import cookiesMiddleware from 'universal-cookie-express';
|
|
18
19
|
import debug from 'debug';
|
|
19
20
|
|
|
20
21
|
import routes from '~/routes';
|
|
@@ -62,10 +63,7 @@ const server = express()
|
|
|
62
63
|
// Support for HEAD requests. Required by start-test utility in CI.
|
|
63
64
|
res.send('');
|
|
64
65
|
})
|
|
65
|
-
.
|
|
66
|
-
plugToRequest(req, res);
|
|
67
|
-
next();
|
|
68
|
-
});
|
|
66
|
+
.use(cookiesMiddleware());
|
|
69
67
|
|
|
70
68
|
const middleware = (config.settings.expressMiddleware || []).filter((m) => m);
|
|
71
69
|
|
|
@@ -106,14 +104,14 @@ function setupServer(req, res, next) {
|
|
|
106
104
|
const browserdetect = detect(req.headers['user-agent']);
|
|
107
105
|
|
|
108
106
|
const lang = new locale.Locales(
|
|
109
|
-
|
|
107
|
+
req.universalCookies.get('I18N_LANGUAGE') ||
|
|
110
108
|
config.settings.defaultLanguage ||
|
|
111
109
|
req.headers['accept-language'],
|
|
112
110
|
)
|
|
113
111
|
.best(supported)
|
|
114
112
|
.toString();
|
|
115
113
|
|
|
116
|
-
const authToken =
|
|
114
|
+
const authToken = req.universalCookies.get('auth_token');
|
|
117
115
|
const initialState = {
|
|
118
116
|
userSession: { ...userSession(), token: authToken },
|
|
119
117
|
form: req.body,
|
|
@@ -132,7 +130,7 @@ function setupServer(req, res, next) {
|
|
|
132
130
|
// Create a new Redux store instance
|
|
133
131
|
const store = configureStore(initialState, history, api);
|
|
134
132
|
|
|
135
|
-
persistAuthToken(store);
|
|
133
|
+
persistAuthToken(store, req);
|
|
136
134
|
|
|
137
135
|
function errorHandler(error) {
|
|
138
136
|
const errorPage = (
|
|
@@ -204,11 +202,13 @@ server.get('/*', (req, res) => {
|
|
|
204
202
|
resetServerContext();
|
|
205
203
|
const markup = renderToString(
|
|
206
204
|
<ChunkExtractorManager extractor={extractor}>
|
|
207
|
-
<
|
|
208
|
-
<
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
|
|
205
|
+
<CookiesProvider cookies={req.universalCookies}>
|
|
206
|
+
<Provider store={store} onError={reactIntlErrorHandler}>
|
|
207
|
+
<StaticRouter context={context} location={req.url}>
|
|
208
|
+
<ReduxAsyncConnect routes={routes} helpers={api} />
|
|
209
|
+
</StaticRouter>
|
|
210
|
+
</Provider>
|
|
211
|
+
</CookiesProvider>
|
|
212
212
|
</ChunkExtractorManager>,
|
|
213
213
|
);
|
|
214
214
|
|
package/src/start-client.jsx
CHANGED
|
@@ -8,6 +8,7 @@ import { ConnectedRouter } from 'connected-react-router';
|
|
|
8
8
|
import { createBrowserHistory } from 'history';
|
|
9
9
|
import { ReduxAsyncConnect } from '@plone/volto/helpers/AsyncConnect';
|
|
10
10
|
import { loadableReady } from '@loadable/component';
|
|
11
|
+
import { CookiesProvider } from 'react-cookie';
|
|
11
12
|
import debug from 'debug';
|
|
12
13
|
import routes from '~/routes';
|
|
13
14
|
import config from '@plone/volto/registry';
|
|
@@ -55,15 +56,17 @@ export default () => {
|
|
|
55
56
|
|
|
56
57
|
loadableReady(() => {
|
|
57
58
|
hydrate(
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
<CookiesProvider>
|
|
60
|
+
<Provider store={store}>
|
|
61
|
+
<IntlProvider onError={reactIntlErrorHandler}>
|
|
62
|
+
<ConnectedRouter history={history}>
|
|
63
|
+
<ScrollToTop>
|
|
64
|
+
<ReduxAsyncConnect routes={routes} helpers={api} />
|
|
65
|
+
</ScrollToTop>
|
|
66
|
+
</ConnectedRouter>
|
|
67
|
+
</IntlProvider>
|
|
68
|
+
</Provider>
|
|
69
|
+
</CookiesProvider>,
|
|
67
70
|
document.getElementById('main'),
|
|
68
71
|
);
|
|
69
72
|
});
|
package/pyvenv.cfg
DELETED