apostrophe 3.4.0 → 3.7.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/CHANGELOG.md +85 -0
- package/README.md +1 -1
- package/deploy-test-count +1 -1
- package/index.js +125 -5
- package/lib/moog-require.js +41 -3
- package/lib/moog.js +20 -8
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +42 -23
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +25 -13
- package/modules/@apostrophecms/area/index.js +9 -0
- package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
- package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +3 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +6 -6
- package/modules/@apostrophecms/asset/index.js +8 -8
- package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +5 -2
- package/modules/@apostrophecms/doc/index.js +13 -3
- package/modules/@apostrophecms/doc-type/index.js +1 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
- package/modules/@apostrophecms/i18n/i18n/en.json +11 -2
- package/modules/@apostrophecms/i18n/i18n/es.json +383 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +380 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +381 -0
- package/modules/@apostrophecms/i18n/index.js +10 -1
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +153 -121
- package/modules/@apostrophecms/image/index.js +2 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +24 -13
- package/modules/@apostrophecms/login/index.js +36 -17
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +8 -0
- package/modules/@apostrophecms/migration/index.js +1 -1
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +1 -1
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
- package/modules/@apostrophecms/module/index.js +1 -1
- package/modules/@apostrophecms/permission/index.js +1 -1
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +4 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +42 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +6 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +64 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Document.js +15 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +23 -0
- package/modules/@apostrophecms/schema/index.js +97 -20
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +8 -5
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
- package/modules/@apostrophecms/template/index.js +61 -36
- package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
- package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
- package/modules/@apostrophecms/ui/index.js +6 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +16 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +3 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_widgets.scss +3 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +2 -1
- package/modules/@apostrophecms/user/index.js +21 -0
- package/modules/@apostrophecms/util/index.js +2 -2
- package/modules/@apostrophecms/util/ui/src/http.js +12 -8
- package/modules/@apostrophecms/widget-type/index.js +1 -1
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
- package/package.json +3 -3
- package/test/extra_node_modules/improve-global/index.js +7 -0
- package/test/extra_node_modules/improve-piece-type/index.js +7 -0
- package/test/improve-overrides.js +30 -0
- package/test/login.js +183 -0
- package/test/modules/@apostrophecms/global/index.js +8 -0
- package/test/modules/fragment-all/views/aux-test.html +7 -0
- package/test/modules/fragment-all/views/fragment.html +5 -0
- package/test/moog.js +47 -0
- package/test/package.json +5 -4
- package/test/reverse-relationship.js +170 -0
- package/test/subdir-project/app.js +3 -0
- package/test/subdir-project.js +26 -0
- package/test/templates.js +7 -1
- package/test-lib/test.js +23 -12
- package/test-lib/util.js +1 -0
|
@@ -98,9 +98,8 @@ module.exports = (self) => {
|
|
|
98
98
|
const source = info.body();
|
|
99
99
|
const input = createRenderInput(info);
|
|
100
100
|
|
|
101
|
-
const req = context.
|
|
101
|
+
const req = context.ctx.__req;
|
|
102
102
|
const env = self.getEnv(req, context.env.opts.module);
|
|
103
|
-
input.apos = self.templateApos;
|
|
104
103
|
|
|
105
104
|
// attach the render caller as a function
|
|
106
105
|
// it's just a string, but we keep
|
|
@@ -108,6 +107,11 @@ module.exports = (self) => {
|
|
|
108
107
|
input.rendercaller = rendercaller;
|
|
109
108
|
|
|
110
109
|
const result = await require('util').promisify((s, args, callback) => {
|
|
110
|
+
args = {
|
|
111
|
+
...self.getRenderArgs(req, {}, context.env.opts.module),
|
|
112
|
+
// Parameters to fragments are top level, they are not in `data`
|
|
113
|
+
...args
|
|
114
|
+
};
|
|
111
115
|
return env.renderString(s, args, callback);
|
|
112
116
|
})(source, input);
|
|
113
117
|
return result;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
options: {
|
|
3
|
-
alias: 'ui'
|
|
3
|
+
alias: 'ui',
|
|
4
|
+
widgetMargin: '20px 0'
|
|
4
5
|
},
|
|
5
6
|
icons: {
|
|
6
7
|
'earth-icon': 'Earth',
|
|
@@ -21,7 +22,10 @@ module.exports = {
|
|
|
21
22
|
if (req.data.user && req.data.user.aposThemePrimary) {
|
|
22
23
|
theme.primary = req.data.user.aposThemePrimary;
|
|
23
24
|
}
|
|
24
|
-
return {
|
|
25
|
+
return {
|
|
26
|
+
theme,
|
|
27
|
+
widgetMargin: self.options.widgetMargin
|
|
28
|
+
};
|
|
25
29
|
}
|
|
26
30
|
};
|
|
27
31
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<span
|
|
2
|
+
<span
|
|
3
|
+
v-apos-tooltip="tooltip"
|
|
4
|
+
class="apos-button__wrapper"
|
|
5
|
+
:class="{ 'apos-button__wrapper--block': modifiers.includes('block') }"
|
|
6
|
+
>
|
|
3
7
|
<component
|
|
4
8
|
:is="href ? 'a' : 'button'"
|
|
5
9
|
v-on="href ? {} : {click: click}"
|
|
@@ -332,6 +336,9 @@ export default {
|
|
|
332
336
|
color: var(--a-base-5);
|
|
333
337
|
}
|
|
334
338
|
}
|
|
339
|
+
.apos-button__label {
|
|
340
|
+
line-height: var(--a-line-tall);
|
|
341
|
+
}
|
|
335
342
|
}
|
|
336
343
|
|
|
337
344
|
.apos-button--subtle {
|
|
@@ -395,8 +402,6 @@ export default {
|
|
|
395
402
|
box-sizing: border-box;
|
|
396
403
|
display: block;
|
|
397
404
|
width: 100%;
|
|
398
|
-
height: 47px;
|
|
399
|
-
max-width: 400px;
|
|
400
405
|
}
|
|
401
406
|
|
|
402
407
|
.apos-button--icon-right {
|
|
@@ -574,6 +579,10 @@ export default {
|
|
|
574
579
|
padding: 3px;
|
|
575
580
|
}
|
|
576
581
|
|
|
582
|
+
.apos-button--uppercase .apos-button__label {
|
|
583
|
+
text-transform: uppercase;
|
|
584
|
+
}
|
|
585
|
+
|
|
577
586
|
.apos-button--inline {
|
|
578
587
|
padding: 0;
|
|
579
588
|
&, &[disabled], &:hover, &:active, &:focus {
|
|
@@ -605,6 +614,10 @@ export default {
|
|
|
605
614
|
display: inline-block;
|
|
606
615
|
}
|
|
607
616
|
|
|
617
|
+
.apos-button__wrapper--block {
|
|
618
|
+
display: block;
|
|
619
|
+
}
|
|
620
|
+
|
|
608
621
|
@keyframes animateGradient {
|
|
609
622
|
0% {
|
|
610
623
|
background-position: 0% 50%;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<component
|
|
8
8
|
:is="icon"
|
|
9
9
|
:size="iconSize"
|
|
10
|
+
:title="title ? title : ''"
|
|
10
11
|
class="apos-indicator__icon"
|
|
11
12
|
:fill-color="iconColor"
|
|
12
13
|
/>
|
|
@@ -30,6 +31,10 @@ export default {
|
|
|
30
31
|
type: [ String, Object, Boolean ],
|
|
31
32
|
default: false
|
|
32
33
|
},
|
|
34
|
+
title: {
|
|
35
|
+
type: [ String, Boolean ],
|
|
36
|
+
default: false
|
|
37
|
+
},
|
|
33
38
|
iconColor: {
|
|
34
39
|
type: String,
|
|
35
40
|
default: 'currentColor'
|
|
@@ -10,11 +10,20 @@ export default {
|
|
|
10
10
|
install(Vue, options) {
|
|
11
11
|
const i18n = options.i18n;
|
|
12
12
|
|
|
13
|
+
const fallbackLng = [ i18n.defaultLocale ];
|
|
14
|
+
// In case the default locale also has inadequate admin UI phrases
|
|
15
|
+
if (fallbackLng[0] !== 'en') {
|
|
16
|
+
fallbackLng.push('en');
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
i18next.init({
|
|
14
20
|
lng: i18n.locale,
|
|
15
|
-
fallbackLng
|
|
21
|
+
fallbackLng,
|
|
16
22
|
resources: {},
|
|
17
|
-
debug: i18n.debug
|
|
23
|
+
debug: i18n.debug,
|
|
24
|
+
interpolation: {
|
|
25
|
+
escapeValue: false
|
|
26
|
+
}
|
|
18
27
|
});
|
|
19
28
|
|
|
20
29
|
for (const [ ns, phrases ] of Object.entries(i18n.i18n[i18n.locale])) {
|
|
@@ -25,6 +34,11 @@ export default {
|
|
|
25
34
|
i18next.addResourceBundle(i18n.defaultLocale, ns, phrases, true, true);
|
|
26
35
|
}
|
|
27
36
|
}
|
|
37
|
+
if ((i18n.locale !== 'en') && (i18n.defaultLocale !== 'en')) {
|
|
38
|
+
for (const [ ns, phrases ] of Object.entries(i18n.i18n.en)) {
|
|
39
|
+
i18next.addResourceBundle('en', ns, phrases, true, true);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
28
42
|
|
|
29
43
|
// Like standard i18next $t, but also with support
|
|
30
44
|
// for just one object argument with at least a `key`
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
.apos-table__header {
|
|
7
7
|
margin-bottom: $spacing-base;
|
|
8
|
-
padding: 12.5px
|
|
8
|
+
padding: 12.5px 15px;
|
|
9
9
|
border-bottom: 1px solid var(--a-base-8);
|
|
10
10
|
color: var(--a-base-3);
|
|
11
11
|
text-align: left;
|
|
@@ -52,12 +52,13 @@ span.apos-table__header-label:hover {
|
|
|
52
52
|
@include apos-transition(all, 0.05s);
|
|
53
53
|
}
|
|
54
54
|
.apos-table__cell {
|
|
55
|
-
padding: 5px;
|
|
55
|
+
padding: 5px 15px;
|
|
56
56
|
border-bottom: 1px solid var(--a-base-10);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.apos-table__cell--context-menu {
|
|
60
|
-
|
|
60
|
+
padding-right: 0;
|
|
61
|
+
padding-left: 0;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
.apos-table__cell-field--context-menu {
|
|
@@ -185,6 +185,19 @@ module.exports = {
|
|
|
185
185
|
if (![ 'guest', 'editor', 'contributor', 'admin' ].includes(doc.role)) {
|
|
186
186
|
throw self.apos.error('invalid', 'The role property of a user must be guest, editor, contributor or admin');
|
|
187
187
|
}
|
|
188
|
+
},
|
|
189
|
+
async invalidatePriorLogins(req, doc, options) {
|
|
190
|
+
const effectiveUserId = req.user && req.user._id;
|
|
191
|
+
// Invalidate prior login sessions if the password field changes or
|
|
192
|
+
// the user is newly marked as disabled.
|
|
193
|
+
if (doc._id && doc._passwordUpdated && (effectiveUserId !== doc._id)) {
|
|
194
|
+
// Invalidate old sessions
|
|
195
|
+
doc.loginInvalidBefore = Date.now();
|
|
196
|
+
// Just delete old bearer tokens
|
|
197
|
+
return self.apos.login.bearerTokens.removeMany({
|
|
198
|
+
userId: doc._id
|
|
199
|
+
});
|
|
200
|
+
}
|
|
188
201
|
}
|
|
189
202
|
},
|
|
190
203
|
// Reflect email and username changes in the safe after deduplicating in the piece
|
|
@@ -347,6 +360,13 @@ module.exports = {
|
|
|
347
360
|
// alone and `safeUser` is not updated.
|
|
348
361
|
//
|
|
349
362
|
// Called automatically by `hashSecrets`, above.
|
|
363
|
+
//
|
|
364
|
+
// The secret property itself is immediately deleted from doc
|
|
365
|
+
// to avoid any risk of accidentally storing it in cleartext.
|
|
366
|
+
// However there is a way to detect that it was updated:
|
|
367
|
+
// if `secret` is `password`, then the `_passwordUpdated` temporary
|
|
368
|
+
// property is set to true. This provides a way to take additional
|
|
369
|
+
// actions stemming from this change in a `beforeSave` handler, etc.
|
|
350
370
|
|
|
351
371
|
async hashSecret(doc, safeUser, secret) {
|
|
352
372
|
if (!doc[secret]) {
|
|
@@ -355,6 +375,7 @@ module.exports = {
|
|
|
355
375
|
const hash = await require('util').promisify(self.pw.hash)(doc[secret]);
|
|
356
376
|
delete doc[secret];
|
|
357
377
|
safeUser[secret + 'Hash'] = hash;
|
|
378
|
+
doc[`_${secret}Updated`] = true;
|
|
358
379
|
},
|
|
359
380
|
|
|
360
381
|
// Verify the given password by checking it against the
|
|
@@ -414,7 +414,7 @@ module.exports = {
|
|
|
414
414
|
return self.insensitiveSortCompare(a[property], b[property]);
|
|
415
415
|
});
|
|
416
416
|
},
|
|
417
|
-
//
|
|
417
|
+
// Compare two strings in a case-insensitive way, returning -1, 0 or 1, suitable for use with sort().
|
|
418
418
|
// If the two strings represent numbers, compare them as numbers for a natural sort order
|
|
419
419
|
// when comparing strings like '4' and '10'.
|
|
420
420
|
insensitiveSortCompare(a, b) {
|
|
@@ -858,7 +858,7 @@ module.exports = {
|
|
|
858
858
|
return _.startCase(o);
|
|
859
859
|
},
|
|
860
860
|
|
|
861
|
-
// check if something is a function (as
|
|
861
|
+
// check if something is a function (as opposed to property)
|
|
862
862
|
isFunction: function(o) {
|
|
863
863
|
return (typeof o === 'function');
|
|
864
864
|
},
|
|
@@ -150,10 +150,12 @@ export default () => {
|
|
|
150
150
|
if (options.busy) {
|
|
151
151
|
if (!busyActive[busyName]) {
|
|
152
152
|
busyActive[busyName] = 0;
|
|
153
|
-
apos.bus
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
if (apos.bus) {
|
|
154
|
+
apos.bus.$emit('busy', {
|
|
155
|
+
active: true,
|
|
156
|
+
name: busyName
|
|
157
|
+
});
|
|
158
|
+
}
|
|
157
159
|
}
|
|
158
160
|
// keep track of nested calls
|
|
159
161
|
busyActive[busyName]++;
|
|
@@ -240,10 +242,12 @@ export default () => {
|
|
|
240
242
|
busyActive[busyName]--;
|
|
241
243
|
if (!busyActive[busyName]) {
|
|
242
244
|
// if no nested calls, disable the "busy" state
|
|
243
|
-
apos.bus
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
if (apos.bus) {
|
|
246
|
+
apos.bus.$emit('busy', {
|
|
247
|
+
active: false,
|
|
248
|
+
name: busyName
|
|
249
|
+
});
|
|
250
|
+
}
|
|
247
251
|
}
|
|
248
252
|
}
|
|
249
253
|
});
|
|
@@ -156,7 +156,7 @@ module.exports = {
|
|
|
156
156
|
self.schema = self.apos.schema.compose({
|
|
157
157
|
addFields: self.apos.schema.fieldsToArray(`Module ${self.__meta.name}`, self.fields),
|
|
158
158
|
arrangeFields: self.apos.schema.groupsToArray(self.fieldsGroups)
|
|
159
|
-
});
|
|
159
|
+
}, self);
|
|
160
160
|
const forbiddenFields = [
|
|
161
161
|
'_id',
|
|
162
162
|
'type'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"vue": "^2.6.14",
|
|
113
113
|
"vue-click-outside-element": "^1.0.13",
|
|
114
114
|
"vue-loader": "^15.9.6",
|
|
115
|
-
"vue-material-design-icons": "
|
|
115
|
+
"vue-material-design-icons": "~4.12.1",
|
|
116
116
|
"vue-style-loader": "^4.1.2",
|
|
117
117
|
"vue-template-compiler": "^2.6.14",
|
|
118
118
|
"vuedraggable": "^2.24.3",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"eslint-loader": "^4.0.2",
|
|
128
128
|
"eslint-plugin-node": "^11.1.0",
|
|
129
129
|
"eslint-plugin-vue": "^7.9.0",
|
|
130
|
-
"mocha": "^
|
|
130
|
+
"mocha": "^9.1.2",
|
|
131
131
|
"nyc": "^15.1.0",
|
|
132
132
|
"replace-in-file": "^6.1.0",
|
|
133
133
|
"vue-eslint-parser": "^7.1.1",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
describe('Improve Overrides', function() {
|
|
5
|
+
|
|
6
|
+
this.timeout(t.timeout);
|
|
7
|
+
|
|
8
|
+
it('"improve" should work, but project level should override it', async function() {
|
|
9
|
+
let apos;
|
|
10
|
+
try {
|
|
11
|
+
apos = await t.create({
|
|
12
|
+
root: module,
|
|
13
|
+
modules: {
|
|
14
|
+
'improve-piece-type': {},
|
|
15
|
+
'improve-global': {}
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
assert(apos.global.options.verifyProjectLevelLoaded);
|
|
19
|
+
assert.strictEqual(apos.user.options.testPieceTypeLevelLoaded, true);
|
|
20
|
+
assert.strictEqual(apos.user.options.testPieceTypeLevel, true);
|
|
21
|
+
assert.strictEqual(apos.global.options.testPieceTypeLevelLoaded, true);
|
|
22
|
+
assert.strictEqual(apos.global.options.testPieceTypeLevel, false);
|
|
23
|
+
assert.strictEqual(apos.global.options.testGlobalLevelLoaded, true);
|
|
24
|
+
assert.strictEqual(apos.global.options.testGlobalLevel, false);
|
|
25
|
+
} finally {
|
|
26
|
+
t.destroy(apos);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
});
|
package/test/login.js
CHANGED
|
@@ -161,4 +161,187 @@ describe('Login', function() {
|
|
|
161
161
|
assert(page.match(/logged out/));
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
+
it('Changing a user\'s password should invalidate sessions for that user', async function() {
|
|
165
|
+
|
|
166
|
+
const jar = apos.http.jar();
|
|
167
|
+
|
|
168
|
+
// establish session
|
|
169
|
+
let page = await apos.http.get(
|
|
170
|
+
'/',
|
|
171
|
+
{
|
|
172
|
+
jar
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
assert(page.match(/logged out/));
|
|
177
|
+
|
|
178
|
+
await apos.http.post(
|
|
179
|
+
'/api/v1/@apostrophecms/login/login',
|
|
180
|
+
{
|
|
181
|
+
method: 'POST',
|
|
182
|
+
body: {
|
|
183
|
+
username: 'HarryPutter',
|
|
184
|
+
password: 'crookshanks',
|
|
185
|
+
session: true
|
|
186
|
+
},
|
|
187
|
+
jar
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
page = await apos.http.get(
|
|
192
|
+
'/',
|
|
193
|
+
{
|
|
194
|
+
jar
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
assert(page.match(/logged in/));
|
|
199
|
+
|
|
200
|
+
const req = apos.task.getReq();
|
|
201
|
+
let user = await apos.user.find(req, {
|
|
202
|
+
username: 'HarryPutter'
|
|
203
|
+
}).toObject();
|
|
204
|
+
assert(user);
|
|
205
|
+
user.password = 'VeryPasswordManySecure🐶';
|
|
206
|
+
await apos.user.update(req, user);
|
|
207
|
+
|
|
208
|
+
page = await apos.http.get(
|
|
209
|
+
'/',
|
|
210
|
+
{
|
|
211
|
+
jar
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
assert(!page.match(/logged in/));
|
|
216
|
+
assert(page.match(/logged out/));
|
|
217
|
+
|
|
218
|
+
// Make sure we can come back from that
|
|
219
|
+
await apos.http.post(
|
|
220
|
+
'/api/v1/@apostrophecms/login/login',
|
|
221
|
+
{
|
|
222
|
+
method: 'POST',
|
|
223
|
+
body: {
|
|
224
|
+
username: 'HarryPutter',
|
|
225
|
+
password: 'VeryPasswordManySecure🐶',
|
|
226
|
+
session: true
|
|
227
|
+
},
|
|
228
|
+
jar
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
page = await apos.http.get(
|
|
233
|
+
'/',
|
|
234
|
+
{
|
|
235
|
+
jar
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
assert(page.match(/logged in/));
|
|
240
|
+
|
|
241
|
+
// So we do not have a stale _passwordUpdated flag
|
|
242
|
+
user = await apos.user.find(req, {
|
|
243
|
+
_id: user._id
|
|
244
|
+
}).toObject();
|
|
245
|
+
|
|
246
|
+
// Unrelated writes to user should not invalidate sessions
|
|
247
|
+
user.title = 'Extra Cool Putter';
|
|
248
|
+
await apos.user.update(req, user);
|
|
249
|
+
|
|
250
|
+
page = await apos.http.get(
|
|
251
|
+
'/',
|
|
252
|
+
{
|
|
253
|
+
jar
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
assert(page.match(/logged in/));
|
|
258
|
+
|
|
259
|
+
// Marking a user account as disabled should invalidate sessions
|
|
260
|
+
user.disabled = true;
|
|
261
|
+
await apos.user.update(req, user);
|
|
262
|
+
|
|
263
|
+
page = await apos.http.get(
|
|
264
|
+
'/',
|
|
265
|
+
{
|
|
266
|
+
jar
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
assert(page.match(/logged out/));
|
|
271
|
+
|
|
272
|
+
// Restore access for next test
|
|
273
|
+
user.disabled = false;
|
|
274
|
+
await apos.user.update(req, user);
|
|
275
|
+
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('Changing a user\'s password should invalidate bearer tokens for that user', async function() {
|
|
279
|
+
|
|
280
|
+
// Log in
|
|
281
|
+
let response = await apos.http.post('/api/v1/@apostrophecms/login/login', {
|
|
282
|
+
body: {
|
|
283
|
+
username: 'HarryPutter',
|
|
284
|
+
password: 'VeryPasswordManySecure🐶'
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
assert(response.token);
|
|
288
|
+
let token = response.token;
|
|
289
|
+
|
|
290
|
+
// For verification: can't do this without an admin bearer token
|
|
291
|
+
await apos.http.get(
|
|
292
|
+
'/api/v1/@apostrophecms/user',
|
|
293
|
+
{
|
|
294
|
+
headers: {
|
|
295
|
+
Authorization: `Bearer ${token}`
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const req = apos.task.getReq();
|
|
301
|
+
const user = await apos.user.find(req, {
|
|
302
|
+
username: 'HarryPutter'
|
|
303
|
+
}).toObject();
|
|
304
|
+
assert(user);
|
|
305
|
+
user.password = 'AnotherLovelyPassword';
|
|
306
|
+
await apos.user.update(req, user);
|
|
307
|
+
|
|
308
|
+
let failed = false;
|
|
309
|
+
try {
|
|
310
|
+
await apos.http.get(
|
|
311
|
+
'/api/v1/@apostrophecms/user',
|
|
312
|
+
{
|
|
313
|
+
headers: {
|
|
314
|
+
Authorization: `Bearer ${token}`
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
// Should NOT work
|
|
319
|
+
assert(false);
|
|
320
|
+
} catch (e) {
|
|
321
|
+
failed = true;
|
|
322
|
+
assert.strictEqual(e.status, 401);
|
|
323
|
+
}
|
|
324
|
+
assert(failed);
|
|
325
|
+
|
|
326
|
+
// Make sure we can come back from that
|
|
327
|
+
response = await apos.http.post('/api/v1/@apostrophecms/login/login', {
|
|
328
|
+
body: {
|
|
329
|
+
username: 'HarryPutter',
|
|
330
|
+
password: 'AnotherLovelyPassword'
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
assert(response.token);
|
|
334
|
+
token = response.token;
|
|
335
|
+
|
|
336
|
+
await apos.http.get(
|
|
337
|
+
'/api/v1/@apostrophecms/user',
|
|
338
|
+
{
|
|
339
|
+
headers: {
|
|
340
|
+
Authorization: `Bearer ${token}`
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
});
|
|
346
|
+
|
|
164
347
|
});
|
package/test/moog.js
CHANGED
|
@@ -237,6 +237,53 @@ describe('moog', function() {
|
|
|
237
237
|
assert(myObject.extended(5) === 20);
|
|
238
238
|
});
|
|
239
239
|
|
|
240
|
+
it('should support inheriting field group fields rather than requiring all fields to be restated', async function() {
|
|
241
|
+
const moog = require('../lib/moog.js')({});
|
|
242
|
+
|
|
243
|
+
moog.define('myObject', {
|
|
244
|
+
cascades: [ 'fields' ],
|
|
245
|
+
fields: {
|
|
246
|
+
add: {
|
|
247
|
+
one: { type: 'string' },
|
|
248
|
+
two: { type: 'string' },
|
|
249
|
+
three: { type: 'string' }
|
|
250
|
+
},
|
|
251
|
+
group: {
|
|
252
|
+
basics: {
|
|
253
|
+
fields: [ 'one', 'two', 'three' ]
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
moog.define('myObject', {
|
|
260
|
+
fields: {
|
|
261
|
+
add: {
|
|
262
|
+
four: { type: 'string' },
|
|
263
|
+
five: { type: 'string' }
|
|
264
|
+
},
|
|
265
|
+
group: {
|
|
266
|
+
basics: {
|
|
267
|
+
fields: [ 'four', 'five' ]
|
|
268
|
+
},
|
|
269
|
+
other: {
|
|
270
|
+
fields: [ 'one' ]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const myObject = await moog.create('myObject', {});
|
|
277
|
+
assert(myObject);
|
|
278
|
+
assert(myObject.fieldsGroups);
|
|
279
|
+
assert(!myObject.fieldsGroups.basics.fields.includes('one'));
|
|
280
|
+
assert(myObject.fieldsGroups.other.fields.includes('one'));
|
|
281
|
+
assert(myObject.fieldsGroups.basics.fields.includes('two'));
|
|
282
|
+
assert(myObject.fieldsGroups.basics.fields.includes('three'));
|
|
283
|
+
assert(myObject.fieldsGroups.basics.fields.includes('four'));
|
|
284
|
+
assert(myObject.fieldsGroups.basics.fields.includes('five'));
|
|
285
|
+
});
|
|
286
|
+
|
|
240
287
|
// ==================================================
|
|
241
288
|
// `redefine` AND `isDefined`
|
|
242
289
|
// ==================================================
|