UNPKG

apostrophe

Version:
545 lines (493 loc) • 15.1 kB
const t = require('../test-lib/test.js'); const assert = require('assert/strict'); describe('user settings', function () { this.timeout(t.timeout); let apos; beforeEach(async function() { await t.destroy(apos); apos = null; }); after(async function() { return t.destroy(apos); }); it('should have settings module', async function() { apos = await t.create({ root: module }); assert(apos.modules['@apostrophecms/settings'], 'module not found'); assert(apos.settings, 'module alias not found'); assert.deepEqual(apos.settings.options.subforms, {}); assert.deepEqual(apos.settings.options.groups, {}); assert.deepEqual(apos.settings.subforms, []); assert.deepEqual(apos.settings.userSchema, []); assert.equal(apos.settings.hasSchema(), false); }); it('should panic on non-existing or forbidden user field in subforms', async function () { apos = await t.create({ root: module }); apos.settings.options.subforms = { test: { fields: [ 'nonExistingField' ] } }; assert.throws( apos.settings.initSubforms, { message: '[@apostrophecms/settings] The field "nonExistingField" is not a valid user field.' } ); const testFields = [ ...apos.settings.systemForbiddenFields ]; for (const field of testFields) { apos.settings.options.subforms = { test: { fields: [ field ] } }; assert.throws( apos.settings.initSubforms, { message: `[@apostrophecms/settings] The field "${field}" is forbidden.` } ); } }); // It should process settings schema and register the subset of the user // schema that is relevant to the configured subforms. it('should init subforms', async function () { apos = await t.create({ root: module, modules: { '@apostrophecms/i18n': { options: { adminLocales: [ { label: 'English', value: 'en' }, { label: 'French', value: 'fr' } ] } }, '@apostrophecms/user': { fields: { add: { firstName: { type: 'string', label: 'First Name' }, lastName: { type: 'string', label: 'Last Name' } } } }, '@apostrophecms/settings': { options: { subforms: { name: { label: 'Name', fields: [ 'firstName', 'lastName' ], preview: '{{ firstName }} {{ lastName }}' }, adminLocale: { fields: [ 'adminLocale' ] } } // No groups configured } } } }); // Schema is processed const schema = apos.settings.userSchema; assert.equal(apos.settings.hasSchema(), true); assert.equal(schema.length, 3); assert(schema.some(field => field.name === 'firstName')); assert(schema.some(field => field.name === 'lastName')); assert(schema.some(field => field.name === 'adminLocale')); const nameSchema = apos.settings.getSubformSchema('name'); assert.equal(nameSchema.length, 2); assert(nameSchema.some(field => field.name === 'firstName')); assert(nameSchema.some(field => field.name === 'lastName')); const adminLocaleSchema = apos.settings.getSubformSchema('adminLocale'); assert.equal(adminLocaleSchema.length, 1); assert(adminLocaleSchema.some(field => field.name === 'adminLocale')); // Subforms are initialized const subforms = apos.settings.subforms; assert.equal(subforms.length, 2); const nameSubform = subforms.find(subform => subform.name === 'name'); assert.equal(nameSubform.label, 'Name'); assert.equal(nameSubform.preview, '{{ firstName }} {{ lastName }}'); assert.deepEqual(nameSubform.schema, nameSchema); assert.deepEqual(nameSubform.group, { name: 'ungrouped', label: 'apostrophe:ungrouped' }); const adminLocaleSubform = subforms.find(subform => subform.name === 'adminLocale'); assert.equal(adminLocaleSubform.label, undefined); assert.equal(adminLocaleSubform.preview, undefined); assert.deepEqual(adminLocaleSubform.schema, adminLocaleSchema); assert.deepEqual(adminLocaleSubform.group, { name: 'ungrouped', label: 'apostrophe:ungrouped' }); // Appropriate browser data is sent const browserData = apos.settings.getBrowserData(apos.task.getReq({ session: {} })); assert.deepEqual(browserData.subforms, subforms); assert.equal(browserData.action, '/api/v1/@apostrophecms/settings'); }); it('should init groups', async function () { apos = await createCommonInstance(); const [ first, second, third, fourth ] = apos.settings.subforms; assert.equal(apos.settings.subforms.length, 4); assert.equal(first.name, 'name'); assert.deepEqual(first.group, { name: 'account', label: 'Account' }); assert.equal(second.name, 'password'); assert.deepEqual(second.group, { name: 'account', label: 'Account' }); assert.equal(third.name, 'adminLocale'); assert.deepEqual(third.group, { name: 'preferences', label: 'Preferences' }); assert.equal(fourth.name, 'display'); assert.deepEqual(fourth.group, { name: 'ungrouped', label: 'apostrophe:ungrouped' }); }); it('should handle protected subforms', async function () { apos = await createCommonInstance(); const [ first, second, third, fourth ] = apos.settings.subforms; assert.equal(apos.settings.subforms.length, 4); assert.equal(first.name, 'name'); assert.equal(first.protection, 'password'); // verify the explicitly set by the config private flag is removed assert.equal(typeof first._passwordChangeForm, 'undefined'); assert.deepEqual(first.fields, [ 'firstName', 'lastName' ]); assert.equal(first.schema.length, 3); // last field is the current password field { const pwdField = first.schema[first.schema.length - 1]; assert.equal(pwdField.type, 'password'); assert.equal(pwdField.name, 'passwordCurrent'); assert.equal(pwdField.required, true); } assert.equal(second.name, 'password'); assert.equal(second.protection, 'password'); assert.equal(second._passwordChangeForm, true); // displayName is removed assert.deepEqual(second.fields, [ 'password' ]); assert(second.help); assert.equal(second.schema[0].name, 'password'); assert.equal(second.schema[0].type, 'password'); assert.equal(second.schema[1].name, 'passwordRepeat'); assert.equal(second.schema[1].type, 'password'); assert.equal(second.schema[2].name, 'passwordCurrent'); assert.equal(second.schema[2].type, 'password'); assert.equal(third.name, 'adminLocale'); assert.equal(!!third.protection, false); assert.equal(third.schema.length, 1); assert.equal(fourth.name, 'display'); assert.equal(fourth.protection, 'password'); assert.deepEqual(fourth.fields, [ 'displayName' ]); assert.equal(fourth.schema.length, 2); // last field is the current password field { const pwdField = fourth.schema[fourth.schema.length - 1]; assert.equal(pwdField.type, 'password'); assert.equal(pwdField.name, 'passwordCurrent'); assert.equal(pwdField.required, true); } }); it('should return 404 when settings user data and no configuration', async function () { apos = await t.create({ root: module }); const { jar } = await login(apos); await assert.rejects( apos.http.get('/api/v1/@apostrophecms/settings', { jar }), { status: 404 } ); }); it('should load settings user data', async function () { apos = await createCommonInstance(); const { jar, user } = await login(apos); const result = await apos.http.get('/api/v1/@apostrophecms/settings', { jar }); assert(result._id); assert.deepEqual(result, { _id: user._id, adminLocale: '', displayName: '', firstName: '', lastName: '' }); }); it('should validate when updating settings user data and no configuration', async function () { apos = await t.create({ root: module }); { const { jar } = await login(apos); await assert.rejects( apos.http.patch('/api/v1/@apostrophecms/settings/name', { jar }), { status: 404 } ); await t.destroy(apos); } apos = await createCommonInstance(); { const { jar } = await login(apos); await assert.rejects( apos.http.patch('/api/v1/@apostrophecms/settings/non-existing', { jar }), { status: 404 } ); } }); it('should update settings', async function () { apos = await createCommonInstance(); const { jar, user } = await login(apos); const result = await apos.http.patch('/api/v1/@apostrophecms/settings/adminLocale', { body: { adminLocale: 'fr' }, jar }); assert(result._id); assert.deepEqual(result, { _id: user._id, adminLocale: 'fr' }); const _user = await apos.user.find(apos.task.getReq(), { _id: user._id }).toObject(); assert.equal(_user.adminLocale, 'fr'); }); it('should change password', async function () { apos = await createCommonInstance(); const { jar } = await login(apos); // Passwords do not match await assert.rejects( apos.http.patch('/api/v1/@apostrophecms/settings/password', { body: { password: 'newpassword', passwordRepeat: 'doesNotMatch', passwordCurrent: 'invalid' }, jar }), function (err) { assert.equal(err.status, 400); assert.equal(err.body.data.errors[0].path, 'passwordRepeat'); assert.equal(err.body.data.errors[0].message, 'invalid'); return true; } ); // Current password is invalid await assert.rejects( apos.http.patch('/api/v1/@apostrophecms/settings/password', { body: { password: 'newpassword', passwordRepeat: 'newpassword', passwordCurrent: 'invalid' }, jar }), function (err) { assert.equal(err.status, 403); assert.equal(err.body.data.path, 'passwordCurrent'); assert.equal(err.body.name, 'forbidden'); return true; } ); await apos.http.patch('/api/v1/@apostrophecms/settings/password', { body: { password: 'newpassword', passwordRepeat: 'newpassword', passwordCurrent: 'editor' }, jar }); await assert.rejects(t.loginAs(apos, 'editor', 'editor')); await logout(apos, 'editor', 'editor', jar); await t.loginAs(apos, 'editor', 'newpassword'); }); it('should password protect subforms', async function () { apos = await createCommonInstance(); const { jar, user } = await login(apos); assert(!user.displayName); // No current password await assert.rejects( apos.http.patch('/api/v1/@apostrophecms/settings/display', { body: { displayName: 'Hacker' }, jar }), function (err) { assert.equal(err.status, 403); assert.equal(err.body.data.path, 'passwordCurrent'); assert.equal(err.body.name, 'forbidden'); return true; } ); // Current password is invalid await assert.rejects( apos.http.patch('/api/v1/@apostrophecms/settings/display', { body: { displayName: 'Editor', passwordCurrent: 'invalid' }, jar }), function (err) { assert.equal(err.status, 403); assert.equal(err.body.data.path, 'passwordCurrent'); assert.equal(err.body.name, 'forbidden'); return true; } ); await apos.http.patch('/api/v1/@apostrophecms/settings/display', { body: { displayName: 'Editor', passwordCurrent: 'editor' }, jar }); const validateUser = await apos.user .find(apos.task.getReq(), { _id: user._id }) .toObject(); assert.equal(validateUser.displayName, 'Editor'); }); }); async function createCommonInstance() { return t.create({ root: module, modules: { '@apostrophecms/i18n': { options: { adminLocales: [ { label: 'English', value: 'en' }, { label: 'French', value: 'fr' } ] } }, '@apostrophecms/user': { fields: { add: { firstName: { type: 'string', label: 'First Name' }, lastName: { type: 'string', label: 'Last Name' }, displayName: { type: 'string', label: 'Display Name' } } } }, '@apostrophecms/settings': { options: { subforms: { display: { fields: [ 'displayName' ], // same as `protection: 'password'` protection: true, // validate it can't happen _passwordChangeForm: true }, adminLocale: { fields: [ 'adminLocale' ] }, name: { label: 'Name', fields: [ 'firstName', 'lastName' ], preview: '{{ firstName }} {{ lastName }}', protection: 'password' }, password: { // Ensure that only `password` will be used fields: [ 'password', 'displayName' ], // Test system protected fields protection: false } }, groups: { account: { label: 'Account', subforms: [ 'name', 'nonExisting', 'password' ] }, preferences: { label: 'Preferences', subforms: [ 'adminLocale' ] } } } } } }); } async function login(apos) { const user = await t.createUser(apos, 'editor', { username: 'editor', password: 'editor' }); const jar = await t.getUserJar(apos, user); await apos.http.get('/', { jar }); return { apos, user, jar }; } async function logout(apos, username, password, jar) { await apos.http.post( '/api/v1/@apostrophecms/login/logout', { body: { username, password, session: true }, jar } ); await apos.http.get( '/', { jar } ); }