diff --git a/addons/api/addon/abilities/account.js b/addons/api/addon/abilities/account.js new file mode 100644 index 000000000..147f73b9f --- /dev/null +++ b/addons/api/addon/abilities/account.js @@ -0,0 +1,15 @@ +import ModelAbility from './model'; + +/** + * Provides abilities for accounts. + */ +export default class AccountAbility extends ModelAbility { + // =permissions + + /** + * @type {boolean} + */ + get canSetPassword() { + return this.hasAuthorizedAction('set-password'); + } +} diff --git a/addons/api/addon/abilities/group.js b/addons/api/addon/abilities/group.js new file mode 100644 index 000000000..810cc82c2 --- /dev/null +++ b/addons/api/addon/abilities/group.js @@ -0,0 +1,22 @@ +import ModelAbility from './model'; + +/** + * Provides abilities for groups. + */ +export default class GroupAbility extends ModelAbility { + // =permissions + + /** + * @type {boolean} + */ + get canAddMembers() { + return this.hasAuthorizedAction('add-members'); + } + + /** + * @type {boolean} + */ + get canRemoveMembers() { + return this.hasAuthorizedAction('remove-members'); + } +} diff --git a/addons/api/addon/abilities/host-set.js b/addons/api/addon/abilities/host-set.js new file mode 100644 index 000000000..088683178 --- /dev/null +++ b/addons/api/addon/abilities/host-set.js @@ -0,0 +1,22 @@ +import ModelAbility from './model'; + +/** + * Provides abilities for host sets. + */ +export default class UserAbility extends ModelAbility { + // =permissions + + /** + * @type {boolean} + */ + get canAddHosts() { + return this.hasAuthorizedAction('add-hosts'); + } + + /** + * @type {boolean} + */ + get canRemoveHosts() { + return this.hasAuthorizedAction('remove-hosts'); + } +} diff --git a/addons/api/addon/abilities/role.js b/addons/api/addon/abilities/role.js new file mode 100644 index 000000000..f0c6d62ec --- /dev/null +++ b/addons/api/addon/abilities/role.js @@ -0,0 +1,29 @@ +import ModelAbility from './model'; + +/** + * Provides abilities for roles. + */ +export default class RoleAbility extends ModelAbility { + // =permissions + + /** + * @type {boolean} + */ + get canSetGrants() { + return this.hasAuthorizedAction('set-grants'); + } + + /** + * @type {boolean} + */ + get canAddPrincipals() { + return this.hasAuthorizedAction('add-principals'); + } + + /** + * @type {boolean} + */ + get canRemovePrincipals() { + return this.hasAuthorizedAction('remove-principals'); + } +} diff --git a/addons/api/addon/abilities/target.js b/addons/api/addon/abilities/target.js index fb6b30d33..11516c3b5 100644 --- a/addons/api/addon/abilities/target.js +++ b/addons/api/addon/abilities/target.js @@ -1,7 +1,7 @@ import ModelAbility from './model'; /** - * Provides a `connect` ability for targets. + * Provides abilities for targets. */ export default class TargetAbility extends ModelAbility { // =permissions diff --git a/addons/api/addon/abilities/user.js b/addons/api/addon/abilities/user.js new file mode 100644 index 000000000..dd2440fe7 --- /dev/null +++ b/addons/api/addon/abilities/user.js @@ -0,0 +1,22 @@ +import ModelAbility from './model'; + +/** + * Provides abilities for users. + */ +export default class UserAbility extends ModelAbility { + // =permissions + + /** + * @type {boolean} + */ + get canAddAccounts() { + return this.hasAuthorizedAction('add-accounts'); + } + + /** + * @type {boolean} + */ + get canRemoveAccounts() { + return this.hasAuthorizedAction('remove-accounts'); + } +} diff --git a/addons/api/app/abilities/account.js b/addons/api/app/abilities/account.js new file mode 100644 index 000000000..e64435a5e --- /dev/null +++ b/addons/api/app/abilities/account.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/account'; diff --git a/addons/api/app/abilities/group.js b/addons/api/app/abilities/group.js new file mode 100644 index 000000000..308aacba4 --- /dev/null +++ b/addons/api/app/abilities/group.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/group'; diff --git a/addons/api/app/abilities/host-set.js b/addons/api/app/abilities/host-set.js new file mode 100644 index 000000000..37ccd5e81 --- /dev/null +++ b/addons/api/app/abilities/host-set.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/host-set'; diff --git a/addons/api/app/abilities/role.js b/addons/api/app/abilities/role.js new file mode 100644 index 000000000..aefb6fd13 --- /dev/null +++ b/addons/api/app/abilities/role.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/role'; diff --git a/addons/api/app/abilities/user.js b/addons/api/app/abilities/user.js new file mode 100644 index 000000000..b02ba6ee2 --- /dev/null +++ b/addons/api/app/abilities/user.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/user'; diff --git a/addons/api/mirage/factories/account.js b/addons/api/mirage/factories/account.js index 5b7a14c09..f5da7b5af 100644 --- a/addons/api/mirage/factories/account.js +++ b/addons/api/mirage/factories/account.js @@ -9,6 +9,7 @@ export default factory.extend({ 'read', 'update', 'delete', + 'set-password', ], attributes() { switch (this.type) { diff --git a/addons/api/mirage/factories/group.js b/addons/api/mirage/factories/group.js index 1f7256543..6a16d3fb0 100644 --- a/addons/api/mirage/factories/group.js +++ b/addons/api/mirage/factories/group.js @@ -9,6 +9,8 @@ export default factory.extend({ 'read', 'update', 'delete', + 'add-members', + 'remove-members', ], id: (i) => `group-${i}`, diff --git a/addons/api/mirage/factories/host-set.js b/addons/api/mirage/factories/host-set.js index def5ff70f..3b88650b7 100644 --- a/addons/api/mirage/factories/host-set.js +++ b/addons/api/mirage/factories/host-set.js @@ -8,6 +8,8 @@ export default factory.extend({ 'read', 'update', 'delete', + 'add-hosts', + 'remove-hosts', ], id: (i) => `host-set-id-${i}`, }); diff --git a/addons/api/mirage/factories/role.js b/addons/api/mirage/factories/role.js index a057fc29f..be13712b8 100644 --- a/addons/api/mirage/factories/role.js +++ b/addons/api/mirage/factories/role.js @@ -9,6 +9,9 @@ export default factory.extend({ 'read', 'update', 'delete', + 'set-grants', + 'add-principals', + 'remove-principals', ], // eslint-disable-next-line ember/avoid-leaking-state-in-ember-objects grant_strings: () => [ diff --git a/addons/api/mirage/factories/user.js b/addons/api/mirage/factories/user.js index b8463506d..837adba44 100644 --- a/addons/api/mirage/factories/user.js +++ b/addons/api/mirage/factories/user.js @@ -8,6 +8,8 @@ export default factory.extend({ 'read', 'update', 'delete', + 'add-accounts', + 'remove-accounts', ], id: (i) => `user-${i}`, }); diff --git a/addons/api/tests/unit/abilities/account-test-.js b/addons/api/tests/unit/abilities/account-test-.js new file mode 100644 index 000000000..afd89329d --- /dev/null +++ b/addons/api/tests/unit/abilities/account-test-.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | Account', function (hooks) { + setupTest(hooks); + + test('it reflects when a given account may set password based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['set-password'], + }; + assert.ok(service.can('setPassword target', model)); + model.authorized_actions = []; + assert.notOk(service.can('setPassword target', model)); + }); +}); diff --git a/addons/api/tests/unit/abilities/group-test.js b/addons/api/tests/unit/abilities/group-test.js new file mode 100644 index 000000000..a53be03c2 --- /dev/null +++ b/addons/api/tests/unit/abilities/group-test.js @@ -0,0 +1,28 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | Group', function (hooks) { + setupTest(hooks); + + test('it reflects when a given group resource may add members based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['add-members'], + }; + assert.ok(service.can('addMembers group', model)); + model.authorized_actions = []; + assert.notOk(service.can('addMembers group', model)); + }); + + test('it reflects when a given group resource may remove members based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['remove-members'], + }; + assert.ok(service.can('removeMembers group', model)); + model.authorized_actions = []; + assert.notOk(service.can('removeMembers group', model)); + }); +}); diff --git a/addons/api/tests/unit/abilities/host-set-test.js b/addons/api/tests/unit/abilities/host-set-test.js new file mode 100644 index 000000000..24b0efa15 --- /dev/null +++ b/addons/api/tests/unit/abilities/host-set-test.js @@ -0,0 +1,28 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | Host Set', function (hooks) { + setupTest(hooks); + + test('it reflects when a given host set resource may add hosts based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['add-hosts'], + }; + assert.ok(service.can('addHosts hostSet', model)); + model.authorized_actions = []; + assert.notOk(service.can('addHosts hostSet', model)); + }); + + test('it reflects when a given hostSet resource may remove hosts based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['remove-hosts'], + }; + assert.ok(service.can('removeHosts hostSet', model)); + model.authorized_actions = []; + assert.notOk(service.can('removeHosts hostSet', model)); + }); +}); diff --git a/addons/api/tests/unit/abilities/role-test.js b/addons/api/tests/unit/abilities/role-test.js new file mode 100644 index 000000000..462743446 --- /dev/null +++ b/addons/api/tests/unit/abilities/role-test.js @@ -0,0 +1,39 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | Role', function (hooks) { + setupTest(hooks); + + test('it reflects when a given role may set grants based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['set-grants'], + }; + assert.ok(service.can('setGrants role', model)); + model.authorized_actions = []; + assert.notOk(service.can('setGrants role', model)); + }); + + test('it reflects when a given role may add principals based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['add-principals'], + }; + assert.ok(service.can('addPrincipals role', model)); + model.authorized_actions = []; + assert.notOk(service.can('addPrincipals role', model)); + }); + + test('it reflects when a given role may remove principals based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['remove-principals'], + }; + assert.ok(service.can('removePrincipals role', model)); + model.authorized_actions = []; + assert.notOk(service.can('removePrincipals role', model)); + }); +}); diff --git a/addons/api/tests/unit/abilities/user-test.js b/addons/api/tests/unit/abilities/user-test.js new file mode 100644 index 000000000..33d8f78e7 --- /dev/null +++ b/addons/api/tests/unit/abilities/user-test.js @@ -0,0 +1,28 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | User', function (hooks) { + setupTest(hooks); + + test('it reflects when a given user resource may add accounts based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['add-accounts'], + }; + assert.ok(service.can('addAccounts user', model)); + model.authorized_actions = []; + assert.notOk(service.can('addAccounts user', model)); + }); + + test('it reflects when a given user resource may remove accounts based on authorized_actions', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const model = { + authorized_actions: ['remove-accounts'], + }; + assert.ok(service.can('removeAccounts user', model)); + model.authorized_actions = []; + assert.notOk(service.can('removeAccounts user', model)); + }); +}); diff --git a/addons/rose/package.json b/addons/rose/package.json index 8a0a04d78..c566535c7 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -29,7 +29,6 @@ "doc:toc": "doctoc README.md" }, "dependencies": { - "babel-loader": "^8.2.2", "ember-cli-babel": "^7.26.6", "ember-cli-htmlbars": "^5.7.1", "ember-focus-trap": "^0.7.0", @@ -39,6 +38,8 @@ "emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774" }, "devDependencies": { + "@babel/core": "^7.15.5", + "babel-loader": "^8.2.2", "@ember/optional-features": "^2.0.0", "@ember/test-helpers": "^2.4.2", "@glimmer/component": "^1.0.4", @@ -47,6 +48,7 @@ "@storybook/addon-actions": "^6.3.8", "@storybook/addon-docs": "^6.3.8", "@storybook/addon-knobs": "^6.3.1", + "@storybook/addons": "^6.3.9", "@storybook/ember": "^6.3.8", "@storybook/ember-cli-storybook": "^0.4.0", "babel-eslint": "^10.1.0", diff --git a/ui/admin/app/components/form/role/grants/index.hbs b/ui/admin/app/components/form/role/grants/index.hbs index 79766a2e1..d6aaad424 100644 --- a/ui/admin/app/components/form/role/grants/index.hbs +++ b/ui/admin/app/components/form/role/grants/index.hbs @@ -1,39 +1,43 @@ - - - {{! Add grant field (only one per form) }} - - - - {{t 'resources.role.grant.actions.create'}} - - - - - - - {{t 'actions.add'}} - - - - - - +{{#if (can 'setGrants role' @model)}} + + + {{! Add grant field (only one per form) }} + + + + {{t + 'resources.role.grant.actions.create' + }} + + + + + + + {{t 'actions.add'}} + + + + + + +{{/if}} @@ -72,23 +77,27 @@ - - {{t 'actions.remove'}} - + {{#if (can 'setGrants role' @model)}} + + {{t 'actions.remove'}} + + {{/if}} {{/each}} - + {{#if (can 'setGrants role' @model)}} + + {{/if}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs index da08d1834..12ca372ce 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/account/-navigation.hbs @@ -5,10 +5,12 @@ {{t 'titles.details'}} {{#if @model.isPassword}} - - {{t 'actions.set-password'}} - + {{#if (can 'setPassword account' @model)}} + + {{t 'actions.set-password'}} + + {{/if}} {{/if}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs b/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs index 253b39ab0..7dbbf977b 100644 --- a/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs +++ b/ui/admin/app/templates/scopes/scope/groups/group/-actions.hbs @@ -3,9 +3,11 @@ @dropdownRight={{true}} as |dropdown| > - - {{t 'resources.group.actions.add-members'}} - + {{#if (can 'addMembers group' @model)}} + + {{t 'resources.group.actions.add-members'}} + + {{/if}} {{#if (can 'delete model' @model)}} - - - {{t 'actions.remove'}} - - + + {{t 'actions.remove'}} + + + {{/if}} {{/each}} diff --git a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs index d78e1b940..d0bae064d 100644 --- a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs +++ b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/-actions.hbs @@ -3,16 +3,18 @@ @dropdownRight={{true}} as |dropdown| > - - {{t 'resources.host-set.actions.create-and-add-host'}} - - - {{t 'resources.host-set.actions.add-hosts'}} - + {{#if (can 'addHosts hostSet' @model)}} + + {{t 'resources.host-set.actions.create-and-add-host'}} + + + {{t 'resources.host-set.actions.add-hosts'}} + + {{/if}} {{#if (can 'delete model' @model)}} - - - {{t 'actions.remove'}} - - + + {{t 'actions.remove'}} + + + {{/if}} {{/each}} diff --git a/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs b/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs index 88add065c..6c01fcc16 100644 --- a/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs +++ b/ui/admin/app/templates/scopes/scope/roles/role/-actions.hbs @@ -3,9 +3,11 @@ @dropdownRight={{true}} as |dropdown| > - - {{t 'resources.role.principal.actions.add-principals'}} - + {{#if (can 'addPrincipals role' @model)}} + + {{t 'resources.role.principal.actions.add-principals'}} + + {{/if}} {{#if (can 'delete model' @model)}} - - - {{t 'actions.remove'}} - - + + {{t 'actions.remove'}} + + + {{/if}} {{/each}} diff --git a/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs b/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs index 280fd98bb..998af871b 100644 --- a/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs +++ b/ui/admin/app/templates/scopes/scope/users/user/-actions.hbs @@ -3,9 +3,11 @@ @dropdownRight={{true}} as |dropdown| > - - {{t 'resources.user.actions.add-accounts'}} - + {{#if (can 'addAccounts user' @model)}} + + {{t 'resources.user.actions.add-accounts'}} + + {{/if}} {{#if (can 'delete model' @model)}} - - - {{t 'actions.remove'}} - - + + {{t 'actions.remove'}} + + + {{/if}} {{/each}} diff --git a/ui/admin/tests/acceptance/accounts/set-password-test.js b/ui/admin/tests/acceptance/accounts/set-password-test.js index 677e9bd7c..89f9ce3e7 100644 --- a/ui/admin/tests/acceptance/accounts/set-password-test.js +++ b/ui/admin/tests/acceptance/accounts/set-password-test.js @@ -49,7 +49,8 @@ module('Acceptance | accounts | set password', function (hooks) { urls.authMethods = `${urls.orgScope}/auth-methods`; urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; urls.accounts = `${urls.authMethod}/accounts`; - urls.setPassword = `${urls.accounts}/${instances.account.id}/set-password`; + urls.account = `${urls.accounts}/${instances.account.id}`; + urls.setPassword = `${urls.account}/set-password`; }); test('visiting account set password', async function (assert) { @@ -59,6 +60,26 @@ module('Acceptance | accounts | set password', function (hooks) { assert.equal(currentURL(), urls.setPassword); }); + test('can navigate to route with proper authorization', async function (assert) { + assert.expect(1); + await visit(urls.account); + assert.ok( + find(`.rose-layout-page-navigation [href="${urls.setPassword}"]`) + ); + }); + + test('cannot navigate to route without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.account.authorized_actions.filter( + (item) => item !== 'set-password' + ); + instances.account.update({ authorized_actions }); + await visit(urls.account); + assert.notOk( + find(`.rose-layout-page-navigation [href="${urls.setPassword}"]`) + ); + }); + test('can set a new password for account', async function (assert) { assert.expect(1); this.server.post( diff --git a/ui/admin/tests/acceptance/groups/members-test.js b/ui/admin/tests/acceptance/groups/members-test.js index 2d615b8ba..295db8404 100644 --- a/ui/admin/tests/acceptance/groups/members-test.js +++ b/ui/admin/tests/acceptance/groups/members-test.js @@ -68,6 +68,16 @@ module('Acceptance | groups | members', function (hooks) { assert.equal(findAll('tbody tr').length, membersCount - 1); }); + test('cannot remove a member without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.group.authorized_actions.filter( + (item) => item !== 'remove-members' + ); + instances.group.update({ authorized_actions }); + await visit(urls.members); + assert.notOk(find('tbody tr .rose-dropdown-button-danger')); + }); + test('shows error message on member remove', async function (assert) { assert.expect(2); this.server.post('/groups/:idMethod', () => { @@ -95,6 +105,22 @@ module('Acceptance | groups | members', function (hooks) { assert.equal(currentURL(), urls.addMembers); }); + test('can navigate to add members with proper authorization', async function (assert) { + assert.expect(1); + await visit(urls.group); + assert.ok(find(`[href="${urls.addMembers}"]`)); + }); + + test('cannot navigate to add members without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.group.authorized_actions.filter( + (item) => item !== 'add-members' + ); + instances.group.update({ authorized_actions }); + await visit(urls.group); + assert.notOk(find(`[href="${urls.addMembers}"]`)); + }); + test('select and save members to add', async function (assert) { assert.expect(3); instances.group.update({ memberIds: [] }); diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js index 3140961c7..2423c62ca 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js @@ -99,6 +99,16 @@ module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) { assert.equal(findAll('tbody tr').length, count - 1); }); + test('cannot remove a host without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.hostSet.authorized_actions.filter( + (item) => item !== 'remove-hosts' + ); + instances.hostSet.update({ authorized_actions }); + await visit(urls.hostSetHosts); + assert.notOk(find('tbody tr .rose-dropdown-button-danger')); + }); + test('shows error message on host remove error', async function (assert) { assert.expect(2); this.server.post('/host-sets/:idMethod', () => { @@ -126,6 +136,22 @@ module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) { assert.equal(currentURL(), urls.addHosts); }); + test('can navigate to add hosts with proper authorization', async function (assert) { + assert.expect(1); + await visit(urls.hostSet); + assert.ok(find(`[href="${urls.addHosts}"]`)); + }); + + test('cannot navigate to add hosts without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.hostSet.authorized_actions.filter( + (item) => item !== 'add-hosts' + ); + instances.hostSet.update({ authorized_actions }); + await visit(urls.hostSet); + assert.notOk(find(`[href="${urls.addHosts}"]`)); + }); + test('select and save hosts to add', async function (assert) { assert.expect(3); instances.hostSet.update({ hostIds: [] }); diff --git a/ui/admin/tests/acceptance/roles/grants-test.js b/ui/admin/tests/acceptance/roles/grants-test.js index d13eb4f46..fd169aa46 100644 --- a/ui/admin/tests/acceptance/roles/grants-test.js +++ b/ui/admin/tests/acceptance/roles/grants-test.js @@ -63,6 +63,26 @@ module('Acceptance | roles | grants', function (hooks) { assert.equal(findAll(`${grantsForm} [name="grant"]`).length, grantsCount); }); + test('cannot set grants without proper authorization', async function (assert) { + assert.expect(4); + const authorized_actions = instances.role.authorized_actions.filter( + (item) => item !== 'set-grants' + ); + instances.role.update({ authorized_actions }); + await visit(urls.grants); + assert.equal( + findAll('main form').length, + 1, + 'New grant form is not displayed.' + ); + assert.notOk( + find('main form button:not([type="submit"])'), + 'Grant delete button is not displayed.' + ); + assert.notOk(find('.rose-form-actions'), 'Form actions are not displayed.'); + assert.ok(find('main form input[disabled]'), 'Grant fields are disabled.'); + }); + test('update a grant', async function (assert) { assert.expect(1); this.server.post( diff --git a/ui/admin/tests/acceptance/roles/principals-test.js b/ui/admin/tests/acceptance/roles/principals-test.js index b26cfe398..998c9acc5 100644 --- a/ui/admin/tests/acceptance/roles/principals-test.js +++ b/ui/admin/tests/acceptance/roles/principals-test.js @@ -69,6 +69,16 @@ module('Acceptance | roles | principals', function (hooks) { assert.equal(findAll('tbody tr').length, principalsCount - 1); }); + test('principal cannot be removed from a role without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.role.authorized_actions.filter( + (item) => item !== 'remove-principals' + ); + instances.role.update({ authorized_actions }); + await visit(urls.rolePrincipals); + assert.notOk(find('tbody tr .rose-dropdown-button-danger')); + }); + test('shows error message on principal remove', async function (assert) { assert.expect(2); this.server.post('/roles/:idMethod', () => { @@ -89,6 +99,16 @@ module('Acceptance | roles | principals', function (hooks) { assert.ok(find('[role="alert"]')); }); + test('cannot navigate to add principals without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.role.authorized_actions.filter( + (item) => item !== 'add-principals' + ); + instances.role.update({ authorized_actions }); + await visit(urls.rolePrincipals); + assert.notOk(find('.rose-layout-page-actions a')); + }); + test('select and save principals to add', async function (assert) { assert.expect(3); instances.role.update({ userIds: [], groupIds: [] }); diff --git a/ui/admin/tests/acceptance/users/accounts-test.js b/ui/admin/tests/acceptance/users/accounts-test.js index 8b7caaba6..fe556f84a 100644 --- a/ui/admin/tests/acceptance/users/accounts-test.js +++ b/ui/admin/tests/acceptance/users/accounts-test.js @@ -61,7 +61,7 @@ module('Acceptance | users | accounts', function (hooks) { assert.equal(findAll('tbody tr').length, accountsCount); }); - test('can remove a account', async function (assert) { + test('can remove an account', async function (assert) { assert.expect(2); await visit(urls.accounts); assert.equal(findAll('tbody tr').length, accountsCount); @@ -69,6 +69,16 @@ module('Acceptance | users | accounts', function (hooks) { assert.equal(findAll('tbody tr').length, accountsCount - 1); }); + test('cannot remove an account without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.user.authorized_actions.filter( + (item) => item !== 'remove-accounts' + ); + instances.user.update({ authorized_actions }); + await visit(urls.accounts); + assert.notOk(find('tbody tr .rose-dropdown-button-danger')); + }); + test('shows error message on account remove', async function (assert) { assert.expect(2); this.server.post('/users/:idMethod', () => { @@ -92,10 +102,27 @@ module('Acceptance | users | accounts', function (hooks) { test('visiting account add accounts', async function (assert) { assert.expect(1); await visit(urls.addAccounts); + await visit(urls.addAccounts); await a11yAudit(); assert.equal(currentURL(), urls.addAccounts); }); + test('can navigate to add accounts with proper authorization', async function (assert) { + assert.expect(1); + await visit(urls.accounts); + assert.ok(find(`[href="${urls.addAccounts}"]`)); + }); + + test('cannot navigate to add accounts without proper authorization', async function (assert) { + assert.expect(1); + const authorized_actions = instances.user.authorized_actions.filter( + (item) => item !== 'add-accounts' + ); + instances.user.update({ authorized_actions }); + await visit(urls.accounts); + assert.notOk(find(`[href="${urls.addAccounts}"]`)); + }); + test('select and save accounts to add', async function (assert) { assert.expect(3); instances.user.update({ accountIds: [] }); diff --git a/ui/desktop/README.md b/ui/desktop/README.md index ba9ca6e88..f80ab75d2 100644 --- a/ui/desktop/README.md +++ b/ui/desktop/README.md @@ -1,6 +1,6 @@ -# Client Connect UI +# Desktop client UI -The client UI for Boundary. +The desktop client UI for Boundary. @@ -31,7 +31,7 @@ You will need the following things properly installed on your computer. ## Installation -See boundary-ui README for installation instructions. +See [boundary-ui README](https://github.com/hashicorp/boundary-ui#installation) for installation instructions. ## Yarn Commands @@ -39,24 +39,32 @@ List of available project commands. `yarn run ` | Command | Description | | ------- | ----------- | -| build:development | Builds the UI in development mode. | | build | Builds the UI for production. | +| build:development | Builds the UI in development mode. | | build:desktop:debianOnMacOS | Builds debian based linux artifacts on MacOS. | | lint | Runs all lint commands. | +| lint:fix | Runs all automatic linting fixes. | | lint:hbs | Lints `hbs` template files. | +| lint:hbs:fix | Runs the automatic linting fix for `hbs` template files. | | lint:js | Lints `js` files. | +| lint:js:fix | Runs the automatic linting fix for `js` files. | | lint:sass | Lints `scss` files. | +| lint:electron | Runs a linter tool to identify misconfigurations and security anti-patterns in Electron. | | format | Runs all auto-formatters. | +| format:hbs | Auto-formats `hbs` files using Prettier. | | format:js | Auto-formats `js` files using Prettier. | | format:sass | Auto-formats `scss` files using Prettier. | -| start | Runs the dummy app local server. | +| start | Runs the dummy app local server as web app. | | start:desktop | Runs the dummy app as an electron app. | +| clean:coverage | Cleans coverage reporting directory. | | test | Runs all tests in random order, with coverage reporting. | +| test:ember | Runs ember tests in random order, with coverage reporting. | | doc:toc | Automatically generates a table of contents for this README file. | ## Running / Development +To run as web app: * `yarn start` * Visit your app at [http://localhost:4200](http://localhost:4200). * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). @@ -90,11 +98,12 @@ These environment variables may be used to customized the build. | `BYPASS_CLI_SETUP` | Disable download and extraction of cli. For development use only. | | `BYPASS_APP_UPDATER` | Disable app updater feature. For development use only. | | `DISABLE_WINDOW_CHROME` | Disable window chrome. For internal use only. | +| `ENABLE_MIRAGE` | Enable (`true`) or disable (`false`) mirage. Default value is `true`. | ### Building for Production Before executing a build, be sure to set any environment variables necessary -for your target [environment](#environment-variables). To build this UI for +for your target [environment](#environment-variables). To build this UI for production, run the following commands from this folder: ```bash diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 2c30fa816..4f0dfb6ca 100755 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -23,13 +23,13 @@ "lint:hbs": "ember-template-lint .", "lint:hbs:fix": "ember-template-lint . --fix", "lint:js": "eslint .", + "lint:js:fix": "eslint . --fix", "lint:sass": "stylelint '**/*.scss'", "lint:electron": "electronegativity -r -i ./electron-app", "format": "npm-run-all format:*", "format:hbs": "prettier --write 'app/**/*.hbs'", "format:js": "prettier --write '{app,config,stories,tests,electron-app/src}/**/*.js' *.js", "format:sass": "prettier --write app/**/*.scss", - "lint:js:fix": "eslint . --fix", "start": "ember serve", "prestart:desktop": "cd electron-app && yarn premake", "start:desktop": "ember electron", diff --git a/yarn.lock b/yarn.lock index 66274fcb4..81f406c7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2693,6 +2693,21 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/addons@^6.3.9": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.3.9.tgz#e12c8d4351b861d67cb6b8d64ed33b1f6b90994f" + integrity sha512-5tRkeHgdb/I/rp3GBkxonDLVsA45Vpgh/vFrsecrS/98wkSYfPEhqrDGLOosJHFrN3J2pznAuNFaA05158uBsw== + dependencies: + "@storybook/api" "6.3.9" + "@storybook/channels" "6.3.9" + "@storybook/client-logger" "6.3.9" + "@storybook/core-events" "6.3.9" + "@storybook/router" "6.3.9" + "@storybook/theming" "6.3.9" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + "@storybook/api@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.8.tgz#251bcf6cc3a4e0b908bea7fb0aa9e48d6c48d720" @@ -2719,6 +2734,32 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/api@6.3.9": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.3.9.tgz#f36c8f01cac602c67b8f22b144e8bd6478039abb" + integrity sha512-lwen3jcY4YbnD8spAZrmXcToed/pwad9QpxkG0GNf6ctcOumN6HIK93fKeJ0vvPYc3v/uq1qKeLyTZ3NrgHQRg== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/channels" "6.3.9" + "@storybook/client-logger" "6.3.9" + "@storybook/core-events" "6.3.9" + "@storybook/csf" "0.0.1" + "@storybook/router" "6.3.9" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.3.9" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^5.3.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/builder-webpack4@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.3.8.tgz#9ffe866fbd50a94b3cf3ecde1fe4d5f29dd337e8" @@ -2817,6 +2858,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/channels@6.3.9": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.3.9.tgz#40907734460fbcb08fe018c794890ffe668e873d" + integrity sha512-ZeHXLFJ43Wn6HJMiGgKUWUMtKcXDoWxL50Qr5Wwbsnmtp2BX7R8aak/Vw9TVT46J86QXkdI3CAKAEvb6esiLRQ== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/client-api@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.3.8.tgz#60e608a5c2afbe6d63a4a063e616bedde8466584" @@ -2849,6 +2899,14 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/client-logger@6.3.9": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.3.9.tgz#ef8c89e96f79927d46dd1dacddb58f3a43154b9a" + integrity sha512-oww+P062SaOQfsTphAQBL6xe5DCv78Po/f/ROk7iYGAbV8HcCCscpzyJSeLfus2CunFYS2ngPcllbvEnqWk7dQ== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/components@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.3.8.tgz#cb84b0245d8784d41e7e6be25a0d5774363e5b87" @@ -2963,6 +3021,13 @@ dependencies: core-js "^3.8.2" +"@storybook/core-events@6.3.9": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.3.9.tgz#17127905a2d7773b6e02f15bfbddb1fd1b0bce75" + integrity sha512-6ELOkroH0Oz7+OR1SqGMKAC1+ufituqSxDp08AyvrHPSYqK/db+P2kSCJBdqyUXTvt8lPvqlCOidkRhGrNB/+A== + dependencies: + core-js "^3.8.2" + "@storybook/core-server@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.3.8.tgz#0d141e6f315b735a442aebc0f424f911456c769c" @@ -3143,6 +3208,22 @@ qs "^6.10.0" ts-dedent "^2.0.0" +"@storybook/router@6.3.9": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.3.9.tgz#a299091426349b9dc0bb8610773918712b453dc4" + integrity sha512-uXNrZS9tsZr6fStIv/MHQfy3xSsc7RLYWbY4wkgZH+y5K97RtuwXgtbx7uyEpsQwse1Z4PikKu/ejN46F0oPGQ== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/client-logger" "6.3.9" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + ts-dedent "^2.0.0" + "@storybook/semver@^7.3.2": version "7.3.2" resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0" @@ -3185,6 +3266,24 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@storybook/theming@6.3.9": + version "6.3.9" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.3.9.tgz#8e4500dacd298a2ca6bf47b5729449688338283c" + integrity sha512-vyMSLvEXrTC4rnUdWLUNmBNeOdBCl0Nt3R6y/laY+LQZ9Ljz/poRTrIYTkmenYieq4N7787s9zHmxvym/ZvKtw== + dependencies: + "@emotion/core" "^10.1.1" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.27" + "@storybook/client-logger" "6.3.9" + core-js "^3.8.2" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.27" + global "^4.4.0" + memoizerific "^1.11.3" + polished "^4.0.5" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/ui@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.3.8.tgz#9fb71befbe76f06c478925610ce998c23260a3f3" @@ -6333,14 +6432,14 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@4.14.2, browserslist@^3.2.6, browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.5, browserslist@^4.16.6, browserslist@^4.16.7: - version "4.17.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9" - integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ== + version "4.17.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.2.tgz#aa15dbd2fab399a399fe4df601bb09363c5458a6" + integrity sha512-jSDZyqJmkKMEMi7SZAgX5UltFdR5NAO43vY0AwTpu4X3sGH7GLLQ83KiUomgrnvZRCeW0yPPnKqnxPqQOER9zQ== dependencies: - caniuse-lite "^1.0.30001259" - electron-to-chromium "^1.3.846" + caniuse-lite "^1.0.30001261" + electron-to-chromium "^1.3.854" escalade "^3.1.1" - nanocolors "^0.1.5" + nanocolors "^0.2.12" node-releases "^1.1.76" bser@2.1.1: @@ -6675,10 +6774,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001259: - version "1.0.30001259" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001259.tgz#ae21691d3da9c4be6144403ac40f71d9f6efd790" - integrity sha512-V7mQTFhjITxuk9zBpI6nYsiTXhcPe05l+364nZjK7MFK/E7ibvYBSAXr4YcA6oPR8j3ZLM/LN+lUqUVAQEUZFg== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001261: + version "1.0.30001263" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001263.tgz#7ce7a6fb482a137585cbc908aaf38e90c53a16a4" + integrity sha512-doiV5dft6yzWO1WwU19kt8Qz8R0/8DgEziz6/9n2FxUasteZNwNNYSmJO3GLBH8lCVE73AB1RPDPAeYbcO5Cvw== capture-exit@^2.0.0: version "2.0.0" @@ -7954,7 +8053,7 @@ debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: dependencies: ms "^2.1.1" -debuglog@^1.0.1: +debuglog@*, debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -8525,10 +8624,10 @@ electron-rebuild@^2.0.3: tar "^6.0.5" yargs "^16.0.0" -electron-to-chromium@^1.3.846: - version "1.3.846" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.846.tgz#a55fd59613dbcaed609e965e3e88f42b08c401d3" - integrity sha512-2jtSwgyiRzybHRxrc2nKI+39wH3AwQgn+sogQ+q814gv8hIFwrcZbV07Ea9f8AmK0ufPVZUvvAG1uZJ+obV4Jw== +electron-to-chromium@^1.3.854: + version "1.3.856" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.856.tgz#75dee0eef9702bffabbf4c1293c989cd3cacb7ba" + integrity sha512-lSezYIe1/p5qkEswAfaQUseOBiwGwuCvRl/MKzOEVe++DcmQ92+43dznDl4rFJ4Zpu+kevhwyIf7KjJevyDA/A== element-resize-detector@^1.2.2: version "1.2.3" @@ -12734,7 +12833,7 @@ import-lazy@^4.0.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== -imurmurhash@^0.1.4: +imurmurhash@*, imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -14431,6 +14530,11 @@ lodash._baseflatten@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash._baseindexof@*: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" + integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= + lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -14439,11 +14543,16 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@^3.0.0: +lodash._bindcallback@*, lodash._bindcallback@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= +lodash._cacheindexof@*: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" + integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= + lodash._createassigner@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11" @@ -14453,12 +14562,19 @@ lodash._createassigner@^3.0.0: lodash._isiterateecall "^3.0.0" lodash.restparam "^3.0.0" +lodash._createcache@*: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" + integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= + dependencies: + lodash._getnative "^3.0.0" + lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@^3.0.0: +lodash._getnative@*, lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= @@ -14676,7 +14792,7 @@ lodash.pick@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= -lodash.restparam@^3.0.0: +lodash.restparam@*, lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= @@ -15695,10 +15811,10 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -nanocolors@^0.1.5: - version "0.1.6" - resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.6.tgz#bc2350d3edfdbfadd7ac018c855ae7c13905a6ad" - integrity sha512-2pvTw6vYRaBLGir2xR7MxaJtyWkrn+C53EpW8yPotG+pdAwBvt0Xwk4VJ6VHLY0aLthVZPvDfm9TdZvrvAm5UQ== +nanocolors@^0.2.12: + version "0.2.12" + resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.2.12.tgz#4d05932e70116078673ea4cc6699a1c56cc77777" + integrity sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug== nanoid@3.1.20: version "3.1.20"