apostrophe 3.11.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/index.js +37 -1
  3. package/lib/moog.js +2 -2
  4. package/modules/@apostrophecms/asset/index.js +24 -9
  5. package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
  6. package/modules/@apostrophecms/attachment/index.js +1 -1
  7. package/modules/@apostrophecms/doc/index.js +9 -3
  8. package/modules/@apostrophecms/doc-type/index.js +2 -2
  9. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +0 -7
  10. package/modules/@apostrophecms/express/index.js +50 -38
  11. package/modules/@apostrophecms/http/index.js +0 -20
  12. package/modules/@apostrophecms/i18n/i18n/en.json +1 -0
  13. package/modules/@apostrophecms/i18n/i18n/sk.json +23 -1
  14. package/modules/@apostrophecms/i18n/index.js +62 -13
  15. package/modules/@apostrophecms/login/index.js +282 -42
  16. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +242 -77
  17. package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +108 -0
  18. package/modules/@apostrophecms/module/index.js +12 -2
  19. package/modules/@apostrophecms/page/index.js +98 -82
  20. package/modules/@apostrophecms/permission/index.js +1 -1
  21. package/modules/@apostrophecms/piece-page-type/index.js +1 -1
  22. package/modules/@apostrophecms/piece-type/index.js +86 -73
  23. package/modules/@apostrophecms/schema/index.js +18 -2
  24. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +12 -5
  25. package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +4 -0
  26. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +18 -0
  27. package/modules/@apostrophecms/util/index.js +3 -9
  28. package/modules/@apostrophecms/util/ui/src/http.js +1 -7
  29. package/package.json +2 -1
  30. package/test/express.js +2 -26
  31. package/test/http.js +0 -24
  32. package/test/login-requirements.js +328 -0
  33. package/test/modules/base-type/i18n/custom/en.json +4 -0
  34. package/test/modules/base-type/i18n/en.json +3 -0
  35. package/test/modules/nested-module-subdirs/example1/index.js +5 -0
  36. package/test/modules/nested-module-subdirs/modules.js +7 -0
  37. package/test/modules/subtype/i18n/custom/en.json +4 -0
  38. package/test/modules/subtype/index.js +7 -0
  39. package/test/pages-rest.js +39 -0
  40. package/test/pieces-page-type.js +63 -0
  41. package/test/static-i18n.js +28 -0
  42. package/test/with-nested-module-subdirs.js +32 -0
  43. package/test/without-nested-module-subdirs.js +31 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.14.0 (2022-02-22)
4
+
5
+ ### Adds
6
+
7
+ * To reduce complications for those implementing caching strategies, the CSRF protection cookie now contains a simple constant string, and is not recorded in `req.session`. This is acceptable because the real purpose of the CSRF check is simply to verify that the browser has sent the cookie at all, which it will not allow a cross-origin script to do.
8
+ * As a result of the above, a session cookie is not generated and sent at all unless `req.session` is actually used or a user logs in. Again, this reduces complications for those implementing caching strategies.
9
+ * When logging out, the session cookie is now cleared in the browser. Formerly the session was destroyed on the server side only, which was sufficient for security purposes but could create caching issues.
10
+ * Uses `express-cache-on-demand` lib to make similar and concurrent requests on pieces and pages faster.
11
+ * Frontend build errors now stop app startup in development, and SCSS and JS/Vue build warnings are visible on the terminal console for the first time.
12
+
13
+ ### Fixes
14
+
15
+ * Fixed a bug when editing a page more than once if the page has a relationship to itself, whether directly or indirectly. Widget ids were unnecessarily regenerated in this situation, causing in-context edits after the first to fail to save.
16
+ * Pages no longer emit double `beforeUpdate` and `beforeSave` events.
17
+ * When the home page extends `@apostrophecms/piece-page-type`, the "show page" URLs for individual pieces should not contain two slashes before the piece slug. Thanks to [Martí Bravo](https://github.com/martibravo) for the fix.
18
+ * Fixes transitions between login page and `afterPasswordVerified` login steps.
19
+ * Frontend build errors now stop the `@apostrophecms/asset:build` task properly in production.
20
+ * `start` replaced with `flex-start` to address SCSS warnings.
21
+ * Dead code removal, as a result of following up on JS/Vue build warnings.
22
+
23
+ ## 3.13.0 - 2022-02-04
24
+
25
+ ### Adds
26
+
27
+ * Additional requirements and related UI may be imposed on native ApostropheCMS logins using the new `requirements` feature, which can be extended in modules that `improve` the `@apostrophecms/login` module. These requirements are not imposed for single sign-on logins via `@apostrophecms/passport-bridge`. See the documentation for more information.
28
+ * Adds latest Slovak translation strings to SK.json in `i18n/` folder. Thanks to [Michael Huna](https://github.com/Miselrkba) for the contribution.
29
+ * Verifies `afterPasswordVerified` requirements one by one when emitting done event, allows to manage errors ans success before to go to the next requirement. Stores and validate each requirement in the token. Checks the new `askForConfirmation` requirement option to go to the next step when emitting done event or waiting for the confirm event (in order to manage success messages). Removes support for `afterSubmit` for now.
30
+
31
+ ### Fixes
32
+
33
+ * Decodes the testReq `param` property in `serveNotFound`. This fixes a problem where page titles using diacritics triggered false 404 errors.
34
+ * Registers the default namespace in the Vue instance of i18n, fixing a lack of support for un-namespaced l10n keys in the UI.
35
+
36
+ ## 3.12.0 - 2022-01-21
37
+
38
+ ### Adds
39
+
40
+ * It is now best practice to deliver namespaced i18n strings as JSON files in module-level subdirectories of `i18n/` named to match the namespace, e.g. `i18n/ourTeam` if the namespace is `ourTeam`. This allows base class modules to deliver phrases to any namespace without conflicting with those introduced at project level. The `i18n` option is now deprecated in favor of the new `i18n` module format section, which is only needed if `browser: true` must be specified for a namespace.
41
+ * Brought back the `nestedModuleSubdirs` feature from A2, which allows modules to be nested in subdirectories if `nestedModuleSubdirs: true` is set in `app.js`. As in A2, module configuration (including activation) can also be grouped in a `modules.js` file in such subdirectories.
42
+
43
+ ### Fixes
44
+
45
+ * Fixes minor inline documentation comments.
46
+ * UI strings that are not registered localization keys will now display properly when they contain a colon (`:`). These were previously interpreted as i18next namespace/key pairs and the "namespace" portion was left out.
47
+ * Fixes a bug where changing the page type immediately after clicking "New Page" would produce a console error. In general, areas and checkboxes now correctly handle their value being changed to `null` by the parent schema after initial startup of the `AposInputArea` or `AposInputCheckboxes` component.
48
+ * It is now best practice to deliver namespaced i18n strings as JSON files in module-level subdirectories of `i18n/` named to match the namespace, e.g. `i18n/ourTeam` if the namespace is `ourTeam`. This allows base class modules to deliver phrases to any namespace without conflicting with those introduced at project level. The `i18n` option is now deprecated in favor of the new `i18n` module format section, which is only needed if `browser: true` must be specified for a namespace.
49
+ * Removes the `@apostrophecms/util` module template helper `indexBy`, which was using a lodash method not included in lodash v4.
50
+ * Removes an unimplemented `csrfExceptions` module section cascade. Use the `csrfExceptions` *option* of any module to set an array of URLs excluded from CSRF protection. More information is forthcoming in the documentation.
51
+ * Fix `[Object Object]` in the console when warning `A permission.can() call was made with a type that has no manager` is printed.
52
+
53
+ ### Changes
54
+
55
+ * Temporarily removes `npm audit` from our automated tests because of a sub-dependency of vue-loader that doesn't actually cause a security vulnerability for apostrophe.
56
+
3
57
  ## 3.11.0 - 2022-01-06
4
58
 
5
59
  ### Adds
@@ -9,6 +63,7 @@
9
63
  ### Fixes
10
64
 
11
65
  * Apostrophe's extension of `req.login` now accounts for the `req.logIn` alias and the skippable `options` parameter, which is relied upon in some `passport` strategies.
66
+ * Apostrophe now warns if a nonexistent widget type is configured for an area field, with special attention to when `-widget` has been erroneously included in the name. For backwards compatibility this is a startup warning rather than a fatal error, as sites generally did operate successfully otherwise with this type of bug present.
12
67
 
13
68
  ### Changes
14
69
 
@@ -16,6 +71,7 @@
16
71
  * Adds deprecation note to `__testDefaults` option. It is not in use, but removing would be a minor BC break we don't need to make.
17
72
  * Allows test modules to use a custom port as an option on the `@apostrophecms/express` module.
18
73
  * Removes the code base pull request template to instead inherit the organization-level template.
74
+ * Adds `npm audit` back to the test scripts.
19
75
 
20
76
  ## 3.10.0 - 2021-12-22
21
77
 
package/index.js CHANGED
@@ -7,6 +7,7 @@ const cluster = require('cluster');
7
7
  const { cpus } = require('os');
8
8
  const process = require('process');
9
9
  const npmResolve = require('resolve');
10
+ const glob = require('glob');
10
11
 
11
12
  let defaults = require('./defaults.js');
12
13
 
@@ -273,6 +274,33 @@ module.exports = async function(options) {
273
274
  return _module;
274
275
  }
275
276
 
277
+ function nestedModuleSubdirs() {
278
+ if (!options.nestedModuleSubdirs) {
279
+ return;
280
+ }
281
+ const configs = glob.sync(self.localModules + '/**/modules.js');
282
+ _.each(configs, function(config) {
283
+ try {
284
+ _.merge(self.options.modules, require(config));
285
+ } catch (e) {
286
+ console.error(stripIndent`
287
+ When nestedModuleSubdirs is active, any modules.js file beneath:
288
+
289
+ ${self.localModules}
290
+
291
+ must export an object containing configuration for Apostrophe modules.
292
+
293
+ The file:
294
+
295
+ ${config}
296
+
297
+ did not parse.
298
+ `);
299
+ throw e;
300
+ }
301
+ });
302
+ }
303
+
276
304
  function autodetectBundles() {
277
305
  const modules = _.keys(self.options.modules);
278
306
  _.each(modules, function(name) {
@@ -406,7 +434,13 @@ module.exports = async function(options) {
406
434
  localModules: self.localModules,
407
435
  defaultBaseClass: '@apostrophecms/module',
408
436
  sections: [ 'helpers', 'handlers', 'routes', 'apiRoutes', 'restApiRoutes', 'renderRoutes', 'middleware', 'customTags', 'components', 'tasks' ],
409
- unparsedSections: [ 'queries', 'extendQueries', 'icons' ]
437
+ nestedModuleSubdirs: self.options.nestedModuleSubdirs,
438
+ unparsedSections: [
439
+ 'queries',
440
+ 'extendQueries',
441
+ 'icons',
442
+ 'i18n'
443
+ ]
410
444
  });
411
445
 
412
446
  self.synth = synth;
@@ -417,6 +451,8 @@ module.exports = async function(options) {
417
451
  self.redefine = self.synth.redefine;
418
452
  self.create = self.synth.create;
419
453
 
454
+ nestedModuleSubdirs();
455
+
420
456
  _.each(self.options.modules, function(options, name) {
421
457
  synth.define(name, options);
422
458
  });
package/lib/moog.js CHANGED
@@ -369,9 +369,9 @@ module.exports = function(options) {
369
369
  }
370
370
 
371
371
  function capture(section) {
372
- that[section] = {};
372
+ that.__meta[section] = {};
373
373
  for (const step of steps) {
374
- that[section][step.__meta.name] = step[section];
374
+ that.__meta[section][step.__meta.name] = step[section];
375
375
  }
376
376
  }
377
377
 
@@ -249,15 +249,23 @@ module.exports = {
249
249
  fs.removeSync(`${bundleDir}/${outputFilename}`);
250
250
  const cssPath = `${bundleDir}/${outputFilename}`.replace(/\.js$/, '.css');
251
251
  fs.removeSync(cssPath);
252
- await Promise.promisify(webpackModule)(require(`./lib/webpack/${name}/webpack.config`)(
253
- {
254
- importFile,
255
- modulesDir,
256
- outputPath: bundleDir,
257
- outputFilename
258
- },
259
- self.apos
260
- ));
252
+ const webpack = Promise.promisify(webpackModule);
253
+ const webpackBaseConfig = require(`./lib/webpack/${name}/webpack.config`);
254
+ const webpackInstanceConfig = webpackBaseConfig({
255
+ importFile,
256
+ modulesDir,
257
+ outputPath: bundleDir,
258
+ outputFilename
259
+ }, self.apos);
260
+ const result = await webpack(webpackInstanceConfig);
261
+ if (result.compilation.errors.length) {
262
+ // Throwing a string is appropriate in a command line task
263
+ throw cleanErrors(result.toString('errors'));
264
+ } else if (result.compilation.warnings.length) {
265
+ self.apos.util.warn(result.toString('errors-warnings'));
266
+ } else if (process.env.APOS_WEBPACK_VERBOSE) {
267
+ self.apos.util.info(result.toString('verbose'));
268
+ }
261
269
  if (fs.existsSync(cssPath)) {
262
270
  fs.writeFileSync(cssPath, self.filterCss(fs.readFileSync(cssPath, 'utf8'), {
263
271
  modulesPrefix: `${self.getAssetBaseUrl()}/modules`
@@ -518,6 +526,13 @@ module.exports = {
518
526
  function getComponentName(component, options, i) {
519
527
  return require('path').basename(component).replace(/\.\w+/, '') + (options.enumerateImports ? `_${i}` : '');
520
528
  }
529
+
530
+ function cleanErrors(errors) {
531
+ // Dev experience: remove confusing and inaccurate webpack warning about module loaders
532
+ // when straightforward JS parse errors occur, stackoverflow is full of people
533
+ // confused by this
534
+ return errors.replace(/(ERROR in[\s\S]*?Module parse failed[\s\S]*)You may need an appropriate loader.*/, '$1');
535
+ }
521
536
  }
522
537
  }
523
538
  };
@@ -12,6 +12,8 @@ module.exports = {
12
12
  'checkbox-blank-icon': 'CheckboxBlankOutline',
13
13
  'check-all-icon': 'CheckAll',
14
14
  'check-bold-icon': 'CheckBold',
15
+ 'check-circle-icon': 'CheckCircle',
16
+ 'check-decagram-icon': 'CheckDecagram',
15
17
  'checkbox-marked-icon': 'CheckboxMarked',
16
18
  'chevron-down-icon': 'ChevronDown',
17
19
  'chevron-left-icon': 'ChevronLeft',
@@ -1114,7 +1114,7 @@ module.exports = {
1114
1114
  await copyOut(uploadfsPath, tempFile);
1115
1115
  await self.sanitizeSvg(tempFile);
1116
1116
  await copyIn(tempFile, uploadfsPath);
1117
- await self.db.update({
1117
+ await self.db.updateOne({
1118
1118
  _id: attachment._id
1119
1119
  }, {
1120
1120
  $set: {
@@ -675,10 +675,10 @@ module.exports = {
675
675
  },
676
676
 
677
677
  // Insert the given document. Called by `.insert()`. You will usually want to
678
- // call the update method of the appropriate doc type manager instead:
678
+ // call the insert method of the appropriate doc type manager instead:
679
679
  //
680
680
  // ```javascript
681
- // self.apos.doc.getManager(doc.type).update(...)
681
+ // self.apos.doc.getManager(doc.type).insert(...)
682
682
  // ```
683
683
  //
684
684
  // However you can override this method to alter the
@@ -1075,7 +1075,13 @@ module.exports = {
1075
1075
  },
1076
1076
  deduplicateWidgetIds(doc) {
1077
1077
  const seen = new Set();
1078
- self.apos.area.walk(doc, area => {
1078
+ self.apos.area.walk(doc, (area, dotPath) => {
1079
+ if (dotPath.includes('_')) {
1080
+ // Ignore relationships so recursive references from a
1081
+ // doc to itself can't result in random regeneration of
1082
+ // widget ids in the doc proper
1083
+ return;
1084
+ }
1079
1085
  for (const widget of area.items || []) {
1080
1086
  if ((!widget._id) || seen.has(widget._id)) {
1081
1087
  widget._id = cuid();
@@ -194,11 +194,11 @@ module.exports = {
194
194
  _id: doc._id.replace(':draft', ':previous')
195
195
  });
196
196
  if (published) {
197
- await self.apos.doc.db.remove({ _id: published._id });
197
+ await self.apos.doc.db.removeOne({ _id: published._id });
198
198
  await self.emit('afterDelete', req, published, { checkForChildren: false });
199
199
  }
200
200
  if (previous) {
201
- await self.apos.doc.db.remove({ _id: previous._id });
201
+ await self.apos.doc.db.removeOne({ _id: previous._id });
202
202
  await self.emit('afterDelete', req, previous, { checkForChildren: false });
203
203
  }
204
204
  },
@@ -23,7 +23,6 @@ import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange'
23
23
  import AposPublishMixin from 'Modules/@apostrophecms/ui/mixins/AposPublishMixin';
24
24
  import AposArchiveMixin from 'Modules/@apostrophecms/ui/mixins/AposArchiveMixin';
25
25
  import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
26
- import klona from 'klona';
27
26
 
28
27
  export default {
29
28
  name: 'AposDocContextMenu',
@@ -263,12 +262,6 @@ export default {
263
262
  // moduleOptions gives us the action, etc. but here we need the schema
264
263
  // which is always type specific, even for pages so get it ourselves
265
264
  let schema = (apos.modules[this.context.type].schema || []).filter(field => apos.schema.components.fields[field.type]);
266
- if (this.restoreOnly) {
267
- schema = klona(schema);
268
- for (const field of schema) {
269
- field.readOnly = true;
270
- }
271
- }
272
265
  // Archive UI is handled via action buttons
273
266
  schema = schema.filter(field => field.name !== 'archived');
274
267
  return schema;
@@ -77,7 +77,15 @@
77
77
  // rolling: true,
78
78
  // secret: 'you should have a secret',
79
79
  // name: self.apos.shortName + '.sid',
80
- // cookie: {}
80
+ // cookie: {
81
+ // path: '/',
82
+ // httpOnly: true,
83
+ // secure: false,
84
+ // // using 'strict' will confuse users if you link to your site
85
+ // // with the expectation that the user is still logged in on arrival.
86
+ // // 'lax' still protects against CSRF attacks
87
+ // sameSite: 'lax'
88
+ // }
81
89
  // }
82
90
  // ```
83
91
  //
@@ -98,17 +106,16 @@
98
106
  //
99
107
  // ### `csrf`
100
108
  //
101
- // By default, Apostrophe implements Angular-compatible [CSRF protection](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
102
- // via an `XSRF-TOKEN` cookie. The `@apostrophecms/asset` module pushes
103
- // a call to the browser to set a jQuery `ajaxPrefilter` which
104
- // adds an `X-XSRF-TOKEN` header to all requests, which must
105
- // match the cookie. This is effective because code running from
106
- // other sites or iframes will not be able to read the cookie and
107
- // send the header.
109
+ // By default, Apostrophe implements [CSRF protection](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
110
+ // by setting a cookie with the value `csrf`, which all legitimate requests originating fromt he page will send
111
+ // back (see the [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)).
112
+ // All modern browsers will refuse to allow a CSRF attacker, such as a malicious `POST`-method `form` tag on a third
113
+ // party site pointing to an Apostrophe site, to send cookies to the Apostrophe site.
108
114
  //
109
115
  // All non-safe HTTP requests (not `GET`, `HEAD`, `OPTIONS` or `TRACE`)
110
- // automatically receive this proection via the csrf middleware, which
111
- // rejects requests in which the CSRF token does not match the header.
116
+ // automatically receive this protection via the csrf middleware, which
117
+ // rejects requests in which the cookie is not present.
118
+ //
112
119
  // If the request was made with a valid api key or bearer token it
113
120
  // bypasses this check.
114
121
  //
@@ -124,12 +131,6 @@
124
131
  // You may need to use this feature when implementing POST form
125
132
  // submissions that do not use AJAX and thus don't send the header.
126
133
  //
127
- // There is also a `minimumExceptions` option, which defaults
128
- // to `[ /login ]`. The login form is the only non-AJAX form
129
- // that ships with Apostrophe. XSRF protection for login forms
130
- // is unnecessary because the password itself is unknown to the
131
- // third party site; it effectively serves as an XSRF token.
132
- //
133
134
  // ### Adding your own middleware
134
135
  //
135
136
  // Use the `middleware` section in your module. That function should
@@ -307,7 +308,13 @@ module.exports = {
307
308
  // "expires" ourselves too
308
309
  const bearer = await self.apos.login.bearerTokens.findOne({
309
310
  _id: req.token,
310
- expires: { $gte: new Date() }
311
+ expires: { $gte: new Date() },
312
+ // requirementsToVerify array should be empty or inexistant
313
+ // for the token to be usable to log in.
314
+ $or: [
315
+ { requirementsToVerify: { $exists: false } },
316
+ { requirementsToVerify: { $ne: [] } }
317
+ ]
311
318
  });
312
319
  return bearer && bearer.userId;
313
320
  }
@@ -317,12 +324,11 @@ module.exports = {
317
324
  },
318
325
  ...((self.options.csrf === false) ? {} : {
319
326
  // Angular-compatible CSRF protection middleware. On safe requests (GET, HEAD, OPTIONS, TRACE),
320
- // set the XSRF-TOKEN cookie if missing. On unsafe requests (everything else),
321
- // make sure our jQuery `ajaxPrefilter` set the X-XSRF-TOKEN header to match the
322
- // cookie.
327
+ // set the csrf cookie if missing.
323
328
  //
324
- // This works because if we're running via a script tag or iframe, we won't
325
- // be able to read the cookie.
329
+ // This works because requests not meeting the expectations of the same-origin policy
330
+ // won't be able to send cookies to the origin at all, even though the value is
331
+ // well-known.
326
332
  csrf(req, res, next) {
327
333
  if (req.csrfExempt) {
328
334
  return next();
@@ -420,7 +426,11 @@ module.exports = {
420
426
  _.defaults(sessionOptions.cookie, {
421
427
  path: '/',
422
428
  httpOnly: true,
423
- secure: false
429
+ secure: false,
430
+ // Ensure that Safari follows the same policy as other modern browsers
431
+ // to prevent CSRF attacks. "lax" just means that navigation links
432
+ // leading to the site will receive the cookie, it is not insecure
433
+ sameSite: 'lax'
424
434
  // maxAge is set for us by connect-mongo,
425
435
  // and defaults to 2 weeks
426
436
  });
@@ -500,28 +510,30 @@ module.exports = {
500
510
  // that this URL should be subject to CSRF.
501
511
 
502
512
  csrfWithoutExceptions(req, res, next) {
503
- let token;
504
513
  // OPTIONS request cannot set a cookie, so manipulating the session here
505
514
  // is not helpful. Do not attempt to set XSRF-TOKEN for OPTIONS
506
515
  if (req.method === 'OPTIONS') {
507
516
  return next();
508
517
  }
509
- // Safe request establishes XSRF-TOKEN in session if not set already
518
+ // Safe request establishes CSRF cookie, whose purpose is only to check
519
+ // that the same-origin policy is followed, not to be unique and secure
520
+ // in itself
510
521
  if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'TRACE') {
511
- token = req.session && req.session['XSRF-TOKEN'];
512
- if (!token) {
513
- token = self.apos.util.generateId();
514
- req.session['XSRF-TOKEN'] = token;
515
- }
516
- // Reset the cookie so that if its lifetime somehow detaches from
517
- // that of the session cookie we're still OK
518
- res.cookie(self.apos.csrfCookieName, token);
522
+ // Use the same standard for the session and CSRF cookies
523
+ res.cookie(self.apos.csrfCookieName, 'csrf', {
524
+ // Will inherit sameSite: 'lax', which is important for
525
+ // CSRF protection in Safari
526
+ ...self.sessionOptions.cookie,
527
+ // 1 year (the limit). The value is known, we are relying
528
+ // on SameSite (modern browsers)
529
+ maxAge: 31536000
530
+ });
519
531
  } else {
520
- // All non-safe requests must be preceded by a safe request that establishes
521
- // the CSRF token, both as a cookie and in the session. Otherwise a user who is logged
522
- // in but doesn't currently have a CSRF token is still vulnerable.
523
- // See options.csrfExceptions
524
- if (!req.cookies[self.apos.csrfCookieName] || req.get('X-XSRF-TOKEN') !== req.cookies[self.apos.csrfCookieName] || req.session['XSRF-TOKEN'] !== req.cookies[self.apos.csrfCookieName]) {
532
+ // Check that the request arrived with the CSRF cookie.
533
+ // This isn't meant to be a unique code that no one could guess,
534
+ // but rather a check that the request from the same origin,
535
+ // as cross-origin requests cannot set cookies on our origin at all.
536
+ if (req.cookies[self.apos.csrfCookieName] !== 'csrf') {
525
537
  res.statusCode = 403;
526
538
  return res.send({
527
539
  name: 'forbidden',
@@ -86,9 +86,6 @@ module.exports = {
86
86
  // `parse` (can be 'json` to always parse the response body as JSON, otherwise the response body is
87
87
  // parsed as JSON only if the content-type is application/json)
88
88
  // `headers` (an object containing header names and values)
89
- // `csrf` (if true, which is the default, and the `jar` contains the CSRF cookie for this Apostrophe site
90
- // due to a previous GET request, send it as the X-XSRF-TOKEN header; if a string, send the current value of the cookie of that name
91
- // in the `jar` as the X-XSRF-TOKEN header; if false, disable this feature)
92
89
  // `fullResponse` (if true, return an object with `status`, `headers` and `body`
93
90
  // properties, rather than returning the body directly; the individual `headers` are canonicalized
94
91
  // to lowercase names. If a header appears multiple times an array is returned for it)
@@ -113,9 +110,6 @@ module.exports = {
113
110
  // `parse` (can be 'json` to always parse the response body as JSON, otherwise the response body is
114
111
  // parsed as JSON only if the content-type is application/json)
115
112
  // `headers` (an object containing header names and values)
116
- // `csrf` (if true, which is the default, and the `jar` contains the CSRF cookie for this Apostrophe site
117
- // due to a previous GET request, send it as the X-XSRF-TOKEN header; if a string, send the current value of the cookie of that name
118
- // in the `jar` as the X-XSRF-TOKEN header; if false, disable this feature)
119
113
  // `fullResponse` (if true, return an object with `status`, `headers` and `body`
120
114
  // properties, rather than returning the body directly; the individual `headers` are canonicalized
121
115
  // to lowercase names. If a header appears multiple times an array is returned for it)
@@ -140,9 +134,6 @@ module.exports = {
140
134
  // `parse` (can be 'json` to always parse the response body as JSON, otherwise the response body is
141
135
  // parsed as JSON only if the content-type is application/json)
142
136
  // `headers` (an object containing header names and values)
143
- // `csrf` (if true, which is the default, and the `jar` contains the CSRF cookie for this Apostrophe site
144
- // due to a previous GET request, send it as the X-XSRF-TOKEN header; if a string, send the current value of the cookie of that name
145
- // in the `jar` as the X-XSRF-TOKEN header; if false, disable this feature)
146
137
  // `fullResponse` (if true, return an object with `status`, `headers` and `body`
147
138
  // properties, rather than returning the body directly; the individual `headers` are canonicalized
148
139
  // to lowercase names. If a header appears multiple times an array is returned for it)
@@ -167,9 +158,6 @@ module.exports = {
167
158
  // `parse` (can be 'json` to always parse the response body as JSON, otherwise the response body is
168
159
  // parsed as JSON only if the content-type is application/json)
169
160
  // `headers` (an object containing header names and values)
170
- // `csrf` (if true, which is the default, and the `jar` contains the CSRF cookie for this Apostrophe site
171
- // due to a previous GET request, send it as the X-XSRF-TOKEN header; if a string, send the current value of the cookie of that name
172
- // in the `jar` as the X-XSRF-TOKEN header; if false, disable this feature)
173
161
  // `fullResponse` (if true, return an object with `status`, `headers` and `body`
174
162
  // properties, rather than returning the body directly; the individual `headers` are canonicalized
175
163
  // to lowercase names. If a header appears multiple times an array is returned for it)
@@ -228,14 +216,6 @@ module.exports = {
228
216
  options.headers = options.headers || {};
229
217
  options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
230
218
  }
231
- if ((options.csrf !== false) && options.jar) {
232
- options.headers = options.headers || {};
233
- const cookieName = ((typeof options.csrf) === 'string') ? options.csrf : self.apos.csrfCookieName;
234
- const cookieValue = self.getCookie(options.jar, url, cookieName);
235
- if (cookieValue != null) {
236
- options.headers['x-xsrf-token'] = cookieValue;
237
- }
238
- }
239
219
  const res = await fetch(url, options);
240
220
  let body;
241
221
  if (options.jar) {
@@ -167,6 +167,7 @@
167
167
  "localizeNewRelated": "Localize new related documents",
168
168
  "localizingBusy": "Localizing Content",
169
169
  "login": "Login",
170
+ "loginErrorGeneric": "An error occurred. Please try again.",
170
171
  "loginDisabled": "Login Disabled",
171
172
  "loginPageBothRequired": "Both the username and the password are required.",
172
173
  "loginPageBadCredentials": "Your credentials are incorrect, or there is no such user",
@@ -3,6 +3,7 @@
3
3
  "addItem": "Pridať položku",
4
4
  "addWidgetType": "Pridať {{ label }}",
5
5
  "admin": "Admin",
6
+ "affirmativeLabel": "Áno, pokračovať.",
6
7
  "altText": "Alternatívny text",
7
8
  "altTextHelp": "Alternatívny popis obrázkov pre zlepšenú prístupnosť",
8
9
  "any": "ľubovoľný",
@@ -10,6 +11,8 @@
10
11
  "applyToSubpages": "Aplikovať na podstánky",
11
12
  "arrayCancelDescription": "Chcete zahodiť zmeny v tomto zozname?",
12
13
  "archive": "Archív",
14
+ "archivingBatchConfirmation": "Naozaj chcete archivovať {{ count }} {{ type }}?",
15
+ "archivingBatchConfirmationButton": "Áno, archivovať obsah.",
13
16
  "archiveImage": "Archivovať obrázok",
14
17
  "archiveOnlyThisPage": "Archivovať len túto stránku",
15
18
  "archivePageAndSubpages": "Archivovať túto stránku a všetky podradené stránky",
@@ -121,6 +124,7 @@
121
124
  "errorPageMessage": "Došlo k chybe",
122
125
  "errorPageStatusCode": "500",
123
126
  "errorPageTitle": "Došlo k chybe v nadpise stánky",
127
+ "errorBatchOperationNoti": "Skupinová operácia {{ operation }} zlyhala.",
124
128
  "everythingElse": "Všetko ostatné",
125
129
  "exit": "Odísť",
126
130
  "fetchPublishedVersionFailed": "Pri načítaní zverejnenej verzie dokumentu sa vyskytla chyba.",
@@ -173,6 +177,8 @@
173
177
  "manageDocType": "Spravovať {{ type }}",
174
178
  "manageDraftSubmissions": "Spravujte návrhy príspevkov",
175
179
  "managePages": "Spravovať stránky",
180
+ "maxLabel": "Max:",
181
+ "maxUi": "Max: {{ number }}",
176
182
  "mediaCreatedDate": "Nahraté: {{ createdDate }}",
177
183
  "mediaDimensions": "Rozmery: {{ width }} 𝗑 {{ height }}",
178
184
  "mediaFileSize": "Veľkosť súboru: {{ fileSize }}",
@@ -180,6 +186,10 @@
180
186
  "mediaMB": "{{ size }}MB",
181
187
  "mediaUploadViaDrop": "Presuňte ich sem, keď budete pripravení",
182
188
  "mediaUploadViaExplorer": "Alebo kliknutím otvorte adresárovú štruktúru",
189
+ "minLabel": "Min:",
190
+ "minUi": "Min: {{ number }}",
191
+ "modify": "Zmeniť",
192
+ "modifyOrDelete": "Zmeniť / Vymazať",
183
193
  "moreOptions": "Viac možností",
184
194
  "moreOperations": "Viac úprav",
185
195
  "multipleEditors": "Viaceré editory",
@@ -202,17 +212,19 @@
202
212
  "notFoundPageStatusCode": "404",
203
213
  "notFoundPageTitle": "404 - Stránka nenájdená",
204
214
  "notInLocale": "Aktuálna stránka v {{ label }} neexistuje. Preložte verziu z {{ currentLocale }}?",
215
+ "notificationClearEventError": "Pri vymazávaní zaregistrovanej notifikačnej udalosti sa vyskytla chyba.",
205
216
  "noTypeFound": "Nenašiel sa žiadny {{ type }}",
206
217
  "parentNotLocalized": "Najprv preložte nadradenú stránku",
207
218
  "notYetPublished": "Tento dokument ešte nebol zverejnený.",
208
219
  "nudgeDown": "Posunúť nadol",
209
220
  "nudgeUp": "Posunúť nahor",
221
+ "numberAdded": "{{ count }} Pridané",
210
222
  "office": "Kancelária",
211
223
  "openGlobal": "Otvorte globálne nastavenia webu",
212
224
  "page": "Stránka",
213
225
  "pageDoesNotExistYet": "Stránka zatiaľ neexistuje",
214
226
  "pageDoesNotExistYetDescription": "Stránka, ktorá poskytuje záznam pre tento diel, ešte nie je k dispozícii ako {{ mode }} v jazykovej mutácii {{ locale }}.",
215
- "pageIsParked": "Táto stránka je zaparkovaná a nemožno ju presunúť",
227
+ "pageIsParked": "Táto stránka je uzamknutá nemožno ju presunúť",
216
228
  "pageNumber": "Stránka {{ number }}",
217
229
  "pageTitle": "Názov stránky",
218
230
  "pages": "Stránky",
@@ -242,6 +254,8 @@
242
254
  "richTextUndo": "Vrátiť späť",
243
255
  "richTextStyleConfigWarning": "Nesprávne nakonfigurovaný štýl: popiska: {{ label }}, {{ tag }}",
244
256
  "password": "Heslo",
257
+ "passwordErrorMin": "Minimum {{ min }} znakov",
258
+ "passwordErrorMax": "Maximum {{ max }} znakov",
245
259
  "passwordResetRequest": "Vaša žiadosť o obnovenie hesla zo stránky {{ site }}",
246
260
  "pasteWidget": "Prilepiť {{ widget }}",
247
261
  "pending": "Čaká na spracovanie",
@@ -277,6 +291,8 @@
277
291
  "relatedDocsOnly": "Len súvisiace dokumenty",
278
292
  "relatedDocsDefinition": "Súvisiace dokumenty sú dokumenty, na ktoré sa odkazuje v tomto dokumente. Obvykle to zahŕňa obrázky, obsah definovaný vzťahmi atď.",
279
293
  "restore": "Obnoviť",
294
+ "restoreBatchConfirmation": "Naozaj chcete obnoviť {{ count }} {{ type }}?",
295
+ "restoreBatchConfirmationButton": "Áno, obnoviť obsah.",
280
296
  "resolveErrorsBeforeSaving": "Pred uložením vyriešte chyby.",
281
297
  "resolveErrorsFirst": "Najprv vyriešte chyby.",
282
298
  "restoreOnlyThisPage": "Obnovte iba túto stránku",
@@ -305,6 +321,12 @@
305
321
  "select": "Vybrať",
306
322
  "selectedMenuItem": "✓ {{ label }}",
307
323
  "selectAll": "Vybrať všetko",
324
+ "selectBoxMessage": "{{ num }} {{ label }} vybraté.",
325
+ "selectBoxMessagePage": "{{ num }} {{ label }} na tejto stránke vybraté.",
326
+ "selectBoxMessageAllButton": "Vybrať všetko {{ num }} {{ label }}.",
327
+ "selectBoxMessageButton": "Vybrať {{ num }} {{ label }}.",
328
+ "selectBoxMessageSelected": "{{ num }} {{ label }} vybraté.",
329
+ "selectBoxMessageAllSelected": "Všetky {{ num }} {{ label }} vybraté.",
308
330
  "deselectAll": "Odznačiť všetko",
309
331
  "selectContent": "Vyberte obsah",
310
332
  "selectContentToLocalize": "Ktorý obsah chcete lokalizovať?",