diff --git a/addons/api/addon/abilities/managed-group.js b/addons/api/addon/abilities/managed-group.js new file mode 100644 index 000000000..dd02a34e9 --- /dev/null +++ b/addons/api/addon/abilities/managed-group.js @@ -0,0 +1,8 @@ +import ModelAbility from './model'; + +/** + * Provides abilities for managed groups. + */ +export default class ManagedGroupAbility extends ModelAbility { + // no op +} diff --git a/addons/api/addon/abilities/role.js b/addons/api/addon/abilities/role.js index f0c6d62ec..b944fa548 100644 --- a/addons/api/addon/abilities/role.js +++ b/addons/api/addon/abilities/role.js @@ -1,9 +1,18 @@ import ModelAbility from './model'; +import { inject as service } from '@ember/service'; + +class InvalidRolePrincipalTypeError extends Error { + name = 'InvalidRolePrincipalTypeError'; +} /** * Provides abilities for roles. */ export default class RoleAbility extends ModelAbility { + // =services + + @service can; + // =permissions /** @@ -26,4 +35,22 @@ export default class RoleAbility extends ModelAbility { get canRemovePrincipals() { return this.hasAuthorizedAction('remove-principals'); } + + /** + * Returns the ability check for a principal model instance. + * Valid role principal models are: User, Group, and ManagedGroup. + * Throws an error if the principal type is invalid. + * @type {boolean} + * @throws {InvalidRolePrincipalTypeError} + */ + get canReadPrincipal() { + const type = this.model.constructor.modelName; + const typeIsValid = + type === 'user' || type === 'group' || type === 'managed-group'; + if (!typeIsValid) + throw new InvalidRolePrincipalTypeError( + `Expected a role principal of type 'user', 'group', or 'managed-group'. Got '${type}'.` + ); + return this.can.can(`read ${type}`, this.model); + } } diff --git a/addons/api/addon/models/credential.js b/addons/api/addon/models/credential.js index d7339b719..a280ae071 100644 --- a/addons/api/addon/models/credential.js +++ b/addons/api/addon/models/credential.js @@ -3,7 +3,7 @@ import GeneratedCredentialModel from '../generated/models/credential'; /** * Supported Credential types. */ -export const types = ['username_password', 'ssh_private_key']; +export const types = ['username_password', 'ssh_private_key', 'json']; export default class CredentialModel extends GeneratedCredentialModel { // =attributes @@ -18,6 +18,14 @@ export default class CredentialModel extends GeneratedCredentialModel { return this.id.startsWith('cred'); } + /** + * True if type is `json`. + * @type {boolean} + */ + get isJSON() { + return this.type === 'json'; + } + /** * True if credential is an unknown type. * @type {boolean} diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js index 08e1a7d82..e4b78e95c 100644 --- a/addons/api/addon/models/host-catalog.js +++ b/addons/api/addon/models/host-catalog.js @@ -1,6 +1,7 @@ import GeneratedHostCatalogModel from '../generated/models/host-catalog'; -export const types = ['aws', 'azure']; +export const types = ['static', 'plugin']; +export const pluginTypes = ['aws', 'azure']; export default class HostCatalogModel extends GeneratedHostCatalogModel { // =attributes @@ -26,7 +27,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel { * @type {boolean} */ get isUnknown() { - return this.isPlugin && !types.includes(this.plugin?.name); + return this.isPlugin && !pluginTypes.includes(this.plugin?.name); } /** diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index ed1c0127f..77c3c6c7b 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -1,5 +1,5 @@ import GeneratedHostSetModel from '../generated/models/host-set'; -import { types } from './host-catalog'; +import { pluginTypes } from './host-catalog'; export default class HostSetModel extends GeneratedHostSetModel { // =attributes @@ -25,7 +25,7 @@ export default class HostSetModel extends GeneratedHostSetModel { * @type {boolean} */ get isUnknown() { - return this.isPlugin && !types.includes(this.plugin?.name); + return this.isPlugin && !pluginTypes.includes(this.plugin?.name); } /** diff --git a/addons/api/addon/models/role.js b/addons/api/addon/models/role.js index 4f2efa720..226c27b2f 100644 --- a/addons/api/addon/models/role.js +++ b/addons/api/addon/models/role.js @@ -1,11 +1,13 @@ import GeneratedRoleModel from '../generated/models/role'; import { attr } from '@ember-data/model'; import { inject as service } from '@ember/service'; +import { all, resolve } from 'rsvp'; export default class RoleModel extends GeneratedRoleModel { // =services @service store; + @service resourceFilterStore; // =attributes @@ -37,6 +39,123 @@ export default class RoleModel extends GeneratedRoleModel { : null; } + /** + * A list of IDs for principals of type `user`. + * @type {string[]} + */ + get userIDs() { + return this.principals + .filter(({ type }) => type === 'user') + .map(({ principal_id }) => principal_id); + } + + /** + * A list of IDs for principals of type `group`. + * @type {string[]} + */ + get groupIDs() { + return this.principals + .filter(({ type }) => type === 'group') + .map(({ principal_id }) => principal_id); + } + + /** + * A list of IDs for principals of type `managed group`. + * @type {string[]} + */ + get managedGroupIDs() { + return this.principals + .filter(({ type }) => type === 'managed group') + .map(({ principal_id }) => principal_id); + } + + /** + * A promise that resolves to an array of User instances. + * When calling this getter, be sure to await resolution + * before interacting with the results. + * @type {Promise[UserModel]} + */ + get users() { + const ids = this.userIDs; + + // Role has prinicipal IDs, + // return a promise which resolves model instances for those IDs + if (ids?.length) { + return this.resourceFilterStore + .queryBy('user', { id: ids }, { scope_id: 'global', recursive: true }) + .then((models) => models.map((model) => model)); + } + + // No principal IDs, + // return a promise resolving to an empty array + return resolve([]); + } + + /** + * A promise that resolves to an array of Group instances. + * When calling this getter, be sure to await resolution + * before interacting with the results. + * @type {Promise[GroupModel]} + */ + get groups() { + const ids = this.groupIDs; + + // Role has prinicipal IDs, + // return a promise which resolves model instances for those IDs + if (ids?.length) { + return this.resourceFilterStore + .queryBy('group', { id: ids }, { scope_id: 'global', recursive: true }) + .then((models) => models.map((model) => model)); + } + + // No principal IDs, + // return a promise resolving to an empty array + return resolve([]); + } + + /** + * A promise that resolves to an array of ManagedGroup instances. + * When calling this getter, be sure to await resolution + * before interacting with the results. + * @type {Promise[ManagedGroupModel]} + */ + get managedGroups() { + const ids = this.managedGroupIDs; + + // Role has prinicipal IDs + // return a promise which resolves model instances for those IDs + if (ids?.length) { + // Collect all auth methods. + const authMethods = this.resourceFilterStore.queryBy( + 'auth-method', + { type: 'oidc' }, + { scope_id: 'global', recursive: true } + ); + + // For each auth method, query all managed groups with IDs + // that match the role's manage group principal IDs. + const managedGroups = authMethods + .then((methods) => + all( + methods.map(({ id: auth_method_id }) => + this.resourceFilterStore + .queryBy('managed-group', { id: ids }, { auth_method_id }) + .then((models) => models.map((model) => model)) + ) + ) + ) + // The result is an array of arrays of model instances (grouped by + // auth methods), so these must be flattened. + .then((grouped) => grouped.flat()); + + return managedGroups; + } + + // No principal IDs, + // return a promise resolving to an empty array + return resolve([]); + } + // =methods /** diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js index 08db9715c..5a77ce566 100644 --- a/addons/api/addon/models/target.js +++ b/addons/api/addon/models/target.js @@ -2,7 +2,9 @@ import GeneratedTargetModel from '../generated/models/target'; import { attr } from '@ember-data/model'; import { inject as service } from '@ember/service'; -export const types = ['tcp', 'ssh']; +export const TYPE_TARGET_TCP = 'tcp'; +export const TYPE_TARGET_SSH = 'ssh'; +export const TYPES_TARGET = Object.freeze([TYPE_TARGET_TCP, TYPE_TARGET_SSH]); export default class TargetModel extends GeneratedTargetModel { // =services @@ -321,7 +323,7 @@ export default class TargetModel extends GeneratedTargetModel { * @type {boolean} */ get isTCP() { - return this.type === 'tcp'; + return this.type === TYPE_TARGET_TCP; } /** @@ -329,6 +331,6 @@ export default class TargetModel extends GeneratedTargetModel { * @type {boolean} */ get isSSH() { - return this.type === 'ssh'; + return this.type === TYPE_TARGET_SSH; } } diff --git a/addons/api/addon/serializers/credential.js b/addons/api/addon/serializers/credential.js index 5c61d93dc..78878b38c 100644 --- a/addons/api/addon/serializers/credential.js +++ b/addons/api/addon/serializers/credential.js @@ -20,7 +20,9 @@ export default class CredentialSerializer extends ApplicationSerializer { delete serialized.attributes.username; if (serialized?.attributes?.json_object) { - serialized.attributes.object = JSON.parse(serialized.attributes.json_object); + serialized.attributes.object = JSON.parse( + serialized.attributes.json_object + ); delete serialized.attributes.json_object; } } diff --git a/addons/api/app/abilities/managed-group.js b/addons/api/app/abilities/managed-group.js new file mode 100644 index 000000000..bf7e304a5 --- /dev/null +++ b/addons/api/app/abilities/managed-group.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/managed-group'; diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 95bbe9ac7..d18bc0676 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -267,14 +267,17 @@ export default function () { this.del('/roles/:id'); this.post( '/roles/:idMethod', - function ({ roles, users, groups }, { params: { idMethod } }) { + function ( + { roles, users, groups, managedGroups }, + { params: { idMethod } } + ) { const attrs = this.normalizedRequestAttrs(); const id = idMethod.split(':')[0]; const method = idMethod.split(':')[1]; const role = roles.find(id); let updatedAttrs = {}; - // Principals is a combined list of users and groups, but in Mirage we've + // Principals is a combined list of users, groups and managed groups, but in Mirage we've // internally modelled them as separate lists. Therefore we must check // the type by looking up the user or group first, then determine which // list to add the principal to. @@ -283,25 +286,31 @@ export default function () { version: attrs.version, userIds: role.userIds, groupIds: role.groupIds, + managedGroupIds: role.managedGroupIds, }; attrs.principalIds.forEach((id) => { const isUser = users.find(id); const isGroup = groups.find(id); + const isManagedGroup = managedGroups.find(id); if (isUser && !updatedAttrs.userIds.includes(id)) { updatedAttrs.userIds.push(id); } if (isGroup && !updatedAttrs.groupIds.includes(id)) { updatedAttrs.groupIds.push(id); } + if (isManagedGroup && !updatedAttrs.managedGroupIds.includes(id)) { + updatedAttrs.managedGroupIds.push(id); + } }); } - // If deleting principals, filter them out of both users and groups, + // If deleting principals, filter the ids out of users, groups and managed group lists, // and in this case don't care about the type if (method === 'remove-principals') { updatedAttrs = { version: attrs.version, userIds: role.userIds, groupIds: role.groupIds, + managedGroupIds: role.managedGroupIds, }; updatedAttrs.userIds = updatedAttrs.userIds.filter((id) => { return !attrs.principalIds.includes(id); @@ -309,6 +318,11 @@ export default function () { updatedAttrs.groupIds = updatedAttrs.groupIds.filter((id) => { return !attrs.principalIds.includes(id); }); + updatedAttrs.managedGroupIds = updatedAttrs.managedGroupIds.filter( + (id) => { + return !attrs.principalIds.includes(id); + } + ); } if (method === 'set-grants') { @@ -580,8 +594,17 @@ export default function () { // managed-groups this.get( '/managed-groups', - ({ managedGroups }, { queryParams: { auth_method_id: authMethodId } }) => { - return managedGroups.where({ authMethodId }); + ( + { managedGroups }, + { queryParams: { auth_method_id: authMethodId, filter } } + ) => { + let resultSet; + if (authMethodId) { + resultSet = managedGroups.where({ authMethodId }); + } else { + resultSet = managedGroups.all(); + } + return resultSet.filter(makeBooleanFilter(filter)); } ); this.post('/managed-groups'); diff --git a/addons/api/mirage/factories/role.js b/addons/api/mirage/factories/role.js index 841558fde..72138f61c 100644 --- a/addons/api/mirage/factories/role.js +++ b/addons/api/mirage/factories/role.js @@ -28,9 +28,13 @@ export default factory.extend({ withPrincipals: trait({ afterCreate(role, server) { const { scope } = role; + const { id: scopeId } = scope; const users = server.createList('user', 2, { scope }); const groups = server.createList('group', 2, { scope }); - role.update({ users, groups }); + const managedGroups = server.schema.managedGroups.where({ + scopeId, + }).models; + role.update({ users, groups, managedGroups }); }, }), diff --git a/addons/api/mirage/factories/target.js b/addons/api/mirage/factories/target.js index 9ea10477e..8afdb93d3 100644 --- a/addons/api/mirage/factories/target.js +++ b/addons/api/mirage/factories/target.js @@ -3,10 +3,11 @@ import { trait } from 'ember-cli-mirage'; import { faker } from '@faker-js/faker'; import permissions from '../helpers/permissions'; import generateId from '../helpers/id'; +import { TYPES_TARGET } from 'api/models/target'; const randomBoolean = (chance = 0.5) => Math.random() < chance; const hostSetChance = 0.3; -const types = ['tcp', 'ssh']; +const types = [...TYPES_TARGET]; export default factory.extend({ authorized_actions: () => @@ -35,13 +36,11 @@ export default factory.extend({ * Generates attributes fields by type. */ afterCreate(target) { - if (target.type === 'tcp' || target.type === 'ssh') { - target.update({ - attributes: { - default_port: faker.datatype.number(), - }, - }); - } + target.update({ + attributes: { + default_port: faker.datatype.number(), + }, + }); }, /** diff --git a/addons/api/mirage/generated/factories/target.js b/addons/api/mirage/generated/factories/target.js index 35e072290..d714b6459 100644 --- a/addons/api/mirage/generated/factories/target.js +++ b/addons/api/mirage/generated/factories/target.js @@ -1,11 +1,12 @@ import { Factory } from 'ember-cli-mirage'; import { faker } from '@faker-js/faker'; +import { TYPE_TARGET_TCP } from 'api/models/target'; /** * GeneratedTargetModelFactory */ export default Factory.extend({ - type: 'tcp', + type: TYPE_TARGET_TCP, name: () => faker.random.words(), session_max_seconds: () => faker.datatype.number(), session_connection_limit: () => faker.datatype.number(), diff --git a/addons/api/mirage/models/role.js b/addons/api/mirage/models/role.js index 97be034f9..54acf6d50 100644 --- a/addons/api/mirage/models/role.js +++ b/addons/api/mirage/models/role.js @@ -5,4 +5,5 @@ export default Model.extend({ scope: belongsTo(), users: hasMany(), groups: hasMany(), + managedGroups: hasMany(), }); diff --git a/addons/api/mirage/scenarios/default.js b/addons/api/mirage/scenarios/default.js index 113dd3194..7ce918221 100644 --- a/addons/api/mirage/scenarios/default.js +++ b/addons/api/mirage/scenarios/default.js @@ -20,7 +20,7 @@ export default function (server) { // Auth const globalAuthMethods = server.createList( 'auth-method', - 1, + 5, { scope: globalScope }, 'withAccountsAndUsersAndManagedGroups' ); @@ -51,8 +51,43 @@ export default function (server) { server.createList('group', 1, { scope: globalScope }, 'withMembers'); server.createList('group', 5, { scope: orgScope }, 'withMembers'); // Role - server.createList('role', 1, { scope: globalScope }, 'withPrincipals'); - server.createList('role', 5, { scope: orgScope }, 'withPrincipals'); + const globalScopeRoles = server.createList( + 'role', + 1, + { scope: globalScope }, + 'withPrincipals' + ); + //create managed groups for role/principals in globalScope + globalScopeRoles.forEach((role) => { + const { scope } = role; + const oidcAuthMethod = globalAuthMethods.filter( + (auth) => auth.type === 'oidc' + )[0]; + const managedGroups = server.createList('managed-group', 2, { + scope, + authMethodId: oidcAuthMethod.id, + }); + role.update({ managedGroups }); + }); + + const orgScopeRoles = server.createList( + 'role', + 5, + { scope: orgScope }, + 'withPrincipals' + ); + //create managed groups for role/principals in orgScope + orgScopeRoles.forEach((role) => { + const { scope } = role; + const oidcAuthMethod = orgAuthMethods.filter( + (auth) => auth.type === 'oidc' + )[0]; + const managedGroups = server.createList('managed-group', 2, { + scope, + authMethodId: oidcAuthMethod.id, + }); + role.update({ managedGroups }); + }); // Other resources server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => { diff --git a/addons/api/mirage/serializers/role.js b/addons/api/mirage/serializers/role.js index db04fe328..14b8d82ac 100644 --- a/addons/api/mirage/serializers/role.js +++ b/addons/api/mirage/serializers/role.js @@ -18,7 +18,19 @@ export default ApplicationSerializer.extend({ id: group.id, type: 'group', })); - json.principals = [...serializedUsers, ...serializedGroups]; + const serializedManagedGroups = model.managedGroups.models.map( + (managedGroup) => ({ + scope_id: model.scope.id, + id: managedGroup.id, + type: 'managed group', + }) + ); + json.principals = [ + ...serializedUsers, + ...serializedGroups, + ...serializedManagedGroups, + ]; + if (!json.principals.length) delete json.principals; // default grant scope diff --git a/addons/api/tests/unit/abilities/role-test.js b/addons/api/tests/unit/abilities/role-test.js index 462743446..b2e8675e5 100644 --- a/addons/api/tests/unit/abilities/role-test.js +++ b/addons/api/tests/unit/abilities/role-test.js @@ -10,9 +10,9 @@ module('Unit | Abilities | Role', function (hooks) { const model = { authorized_actions: ['set-grants'], }; - assert.ok(service.can('setGrants role', model)); + assert.true(service.can('setGrants role', model)); model.authorized_actions = []; - assert.notOk(service.can('setGrants role', model)); + assert.false(service.can('setGrants role', model)); }); test('it reflects when a given role may add principals based on authorized_actions', function (assert) { @@ -21,9 +21,9 @@ module('Unit | Abilities | Role', function (hooks) { const model = { authorized_actions: ['add-principals'], }; - assert.ok(service.can('addPrincipals role', model)); + assert.true(service.can('addPrincipals role', model)); model.authorized_actions = []; - assert.notOk(service.can('addPrincipals role', model)); + assert.false(service.can('addPrincipals role', model)); }); test('it reflects when a given role may remove principals based on authorized_actions', function (assert) { @@ -32,8 +32,47 @@ module('Unit | Abilities | Role', function (hooks) { const model = { authorized_actions: ['remove-principals'], }; - assert.ok(service.can('removePrincipals role', model)); + assert.true(service.can('removePrincipals role', model)); model.authorized_actions = []; - assert.notOk(service.can('removePrincipals role', model)); + assert.false(service.can('removePrincipals role', model)); + }); + + test('it proxies role principal read ability to the appropriate ability type', function (assert) { + assert.expect(6); + const service = this.owner.lookup('service:can'); + const user = { + constructor: { modelName: 'user' }, + authorized_actions: [], + }; + const group = { + constructor: { modelName: 'group' }, + authorized_actions: [], + }; + const managedGroup = { + constructor: { modelName: 'managed-group' }, + authorized_actions: [], + }; + assert.false(service.can('readPrincipal role', user)); + assert.false(service.can('readPrincipal role', group)); + assert.false(service.can('readPrincipal role', managedGroup)); + user.authorized_actions.push('read'); + group.authorized_actions.push('read'); + managedGroup.authorized_actions.push('read'); + assert.true(service.can('readPrincipal role', user)); + assert.true(service.can('readPrincipal role', group)); + assert.true(service.can('readPrincipal role', managedGroup)); + }); + + test('it throws an error for unknown principal types on principal read check', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const account = { + constructor: { modelName: 'account' }, + authorized_actions: ['read'], + }; + assert.throws(() => { + service.can('readPrincipal role', account); + }); + assert.true(service.can('read account', account)); }); }); diff --git a/addons/api/tests/unit/models/credential-test.js b/addons/api/tests/unit/models/credential-test.js index 21b5b98ce..a0f0497e2 100644 --- a/addons/api/tests/unit/models/credential-test.js +++ b/addons/api/tests/unit/models/credential-test.js @@ -22,6 +22,22 @@ module('Unit | Model | credential', function (hooks) { assert.false(modelC.isStatic); }); + test('it has isJSON property and returns the expected values', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const modelA = store.createRecord('credential', { type: 'json' }); + const modelB = store.createRecord('credential', { + type: 'username_password', + }); + const modelC = store.createRecord('credential', { + type: 'ssh_private_key', + }); + + assert.true(modelA.isJSON); + assert.false(modelB.isJSON); + assert.false(modelC.isJSON); + }); + test('it has isUnknown property and returns the expected values', async function (assert) { assert.expect(3); const store = this.owner.lookup('service:store'); diff --git a/addons/api/tests/unit/models/role-test.js b/addons/api/tests/unit/models/role-test.js index 9d64e9ac2..fc0c5d8e8 100644 --- a/addons/api/tests/unit/models/role-test.js +++ b/addons/api/tests/unit/models/role-test.js @@ -64,6 +64,7 @@ module('Unit | Model | role', function (hooks) { principals: [ { id: '1', type: 'user' }, { id: '3', type: 'group' }, + { id: '5', type: 'managed group' }, ], version: 1, scope: { @@ -84,7 +85,7 @@ module('Unit | Model | role', function (hooks) { (schema, request) => { const body = JSON.parse(request.requestBody); assert.deepEqual(body, { - principal_ids: ['1', '3'], + principal_ids: ['1', '3', '5'], version: 1, }); return { id: '123abc' }; @@ -101,6 +102,7 @@ module('Unit | Model | role', function (hooks) { principals: [ { id: '1', type: 'user' }, { id: '3', type: 'group' }, + { id: '5', type: 'managed group' }, ], version: 1, scope: { @@ -111,7 +113,7 @@ module('Unit | Model | role', function (hooks) { }, }); const model = store.peekRecord('role', '123abc'); - await model.removePrincipals(['1', '3']); + await model.removePrincipals(['1', '3', '5']); }); test('it has a `removePrincipal` method that deletes a single principal using `removePrincipals` method', async function (assert) { @@ -138,6 +140,7 @@ module('Unit | Model | role', function (hooks) { principals: [ { id: '1', type: 'user' }, { id: '3', type: 'group' }, + { id: '5', type: 'managed group' }, ], version: 1, scope: { diff --git a/addons/api/tests/unit/models/target-test.js b/addons/api/tests/unit/models/target-test.js index eca4d54a5..86dd93c0f 100644 --- a/addons/api/tests/unit/models/target-test.js +++ b/addons/api/tests/unit/models/target-test.js @@ -1,6 +1,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Unit | Model | target', function (hooks) { setupTest(hooks); @@ -383,7 +384,7 @@ module('Unit | Model | target', function (hooks) { assert.expect(2); const store = this.owner.lookup('service:store'); const modelSSH = store.createRecord('target', { - type: 'ssh', + type: TYPE_TARGET_SSH, }); assert.true(modelSSH.isSSH); assert.false(modelSSH.isTCP); @@ -393,7 +394,7 @@ module('Unit | Model | target', function (hooks) { assert.expect(2); const store = this.owner.lookup('service:store'); const modelTCP = store.createRecord('target', { - type: 'tcp', + type: TYPE_TARGET_TCP, }); assert.true(modelTCP.isTCP); assert.false(modelTCP.isSSH); diff --git a/addons/api/tests/unit/serializers/auth-method-test.js b/addons/api/tests/unit/serializers/auth-method-test.js index 17043ffba..23c17fb7e 100644 --- a/addons/api/tests/unit/serializers/auth-method-test.js +++ b/addons/api/tests/unit/serializers/auth-method-test.js @@ -182,7 +182,6 @@ module('Unit | Serializer | auth method', function (hooks) { signing_algorithms, idp_ca_certs, } = record; - console.log(record); assert.deepEqual(account_claim_maps, [ { from: 'from', to: 'to' }, { from: 'foo', to: 'bar' }, diff --git a/addons/api/tests/unit/serializers/target-test.js b/addons/api/tests/unit/serializers/target-test.js index fde87e20d..29864c838 100644 --- a/addons/api/tests/unit/serializers/target-test.js +++ b/addons/api/tests/unit/serializers/target-test.js @@ -1,5 +1,6 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Unit | Serializer | target', function (hooks) { setupTest(hooks); @@ -26,7 +27,7 @@ module('Unit | Serializer | target', function (hooks) { }, default_port: 1234, version: 1, - type: 'tcp', + type: TYPE_TARGET_TCP, }); const snapshot = record._createSnapshot(); snapshot.adapterOptions = {}; @@ -35,7 +36,7 @@ module('Unit | Serializer | target', function (hooks) { name: 'User', description: 'Description', version: 1, - type: 'tcp', + type: TYPE_TARGET_TCP, scope_id: 'org_1', session_max_seconds: 28800, session_connection_limit: null, @@ -124,7 +125,7 @@ module('Unit | Serializer | target', function (hooks) { name: 'User', description: 'Description', version: 1, - type: 'ssh', + type: TYPE_TARGET_SSH, worker_filter: 'worker', default_port: 1234, scope: { @@ -137,7 +138,7 @@ module('Unit | Serializer | target', function (hooks) { name: 'User', description: 'Description', version: 1, - type: 'ssh', + type: TYPE_TARGET_SSH, scope_id: 'org_1', session_max_seconds: 28800, session_connection_limit: null, diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml index efa8a811c..35787d6d8 100644 --- a/addons/core/translations/en-us.yaml +++ b/addons/core/translations/en-us.yaml @@ -41,7 +41,6 @@ descriptions: neither-list-nor-create: You do not have permission to list or create {resource}. no-results: We found no matching records. choose-a-provider: There are several built-in plugins for the most common providers. You can choose from one of the included plugins or use a custom plugin. - choose-a-provider-azure: Boundary does not yet support rotating credentials. provider: This is the provider for this host catalog. The format of the host set filters are specific to each provider. questions: delete-confirm: Are you sure you want to delete this resource? diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index 8a2286444..682bd73f8 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -150,6 +150,7 @@ secret: help: The client secret value generated by Azure for Boundary to use for authentication. disable_credential_rotation: label: Disable credential rotation + help: Azure host catalogs do not yet support credential rotation. dns_names: label: Latest DNS Names host: @@ -163,4 +164,4 @@ tags: label: Tags secret-editor: details: This secret is saved but won't be displayed - edit: Click to replace this secret \ No newline at end of file + edit: Click to replace this secret diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 32f934c43..eacbb9877 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -93,13 +93,13 @@ host-catalog: delete: Delete Host Catalog help: static: Provide a list of hosts and host sets with static addresses - dynamic: Automate discovery of target hosts and services on Azure or AWS + plugin: Automate discovery of target hosts and services on Azure or AWS types: static: Static unknown: Plugin aws: AWS azure: Azure - dynamic: Dynamic + plugin: Dynamic form: aws_region: label: AWS Region @@ -271,6 +271,7 @@ role: types: user: User group: Group + managed-group: Managed Group actions: add-principals: Add Principals grant: @@ -304,11 +305,15 @@ target: add-brokered-credential-sources: Add Brokered Credentials add-injected-application-credential-sources: Add Injected Application Credentials types: - tcp: TCP + tcp: Generic TCP ssh: SSH help: - tcp: Connections are made over TCP to support a broad range of protocols, including SSH, RDP, databases, and more. This is the most general-purpose type. - ssh: Protocol-aware connections are made over SSH. Use when requiring SSH connections to remote hosts. + tcp: Generic TCP supports a broad range of connection types. + ssh: Protocol-aware SSH with support for credential injection. + form: + type: + label: Type + help: Target type is the protocol with which end users should connect to this target. Choose Generic TCP for broad support of common protocols including RDP, K8s, many databases, and more. host-source: title: Host Source title_plural: Host Sources @@ -455,6 +460,9 @@ credential: private_key_passphrase: label: Passphrase help: Optional. + json: + label: JSON + help: This is the JSON blob that provides credentials to the host. After this data is saved, you will not be able to edit it again without replacing it. scope: title: Scope title_plural: Scopes diff --git a/addons/rose/addon/components/rose/form/radio/card/index.js b/addons/rose/addon/components/rose/form/radio/card/index.js deleted file mode 100644 index f4aaab3bb..000000000 --- a/addons/rose/addon/components/rose/form/radio/card/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import RoseFormRadioRadioComponent from '../radio'; -import { deprecate } from '@ember/debug'; - -export default class RoseFormRadioCardComponent extends RoseFormRadioRadioComponent { - // =attributes - layout = this.args.layout; - className = `rose-form-radio-card ${this.layout}`; - - constructor() { - super(...arguments); - deprecate( - `Rose Form/Radio/Card component is deprecated, use HDS Form/RadioCard instead. For more info, refer to `, - false, - { - id: 'rose.form.radio.card', - until: '4.8.0', - for: 'rose', - since: { - enabled: '3.28.8', - }, - url: 'https://boundary-ui-storybook.vercel.app/?path=/docs/rose-form-radio-radio-card--card', - } - ); - } -} diff --git a/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx index 5cd8de8ab..9d284938c 100644 --- a/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx +++ b/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx @@ -1,15 +1,12 @@ import { Meta, Story, Preview } from '@storybook/addon-docs'; import { hbs } from 'ember-cli-htmlbars'; - + -# HDS::Form::RadioCard (Recommended) +# RadioCard (removed) -> **_NOTE:_** Replaces the deprecated Rose/Form/Radio/Card. - -Refer to the [HDS Form::RadioCard -documentation](https://design-system-components-hashicorp.vercel.app/components/form/radio-card) -for a complete overview +Rose/Form/Select has been removed in favor of +[HDS::Form::RadioCard](https://design-system-components-hashicorp.vercel.app/components/form/radio-card). ```handlebars @@ -44,78 +41,3 @@ export const HDSTemplate = (args) => ({ {HDSTemplate.bind({})} - -# Rose/Form/Radio/Card (Deprecated) - -Rose/Form/Radio/Card has been deprecated in favor of [HDS::Form::RadioCard](https://design-system-components-hashicorp.vercel.app/components/form/radio-card) - -### usage: - -# Form Radiofield in a wide format - -Renders a form radiofield in a Wide format. - -```handlebars - -``` - -Renders a form radiofield in a tile format. - -```handlebars - -``` - -export const Template = (args) => ({ - template: hbs` - `, - context: args, -}); - - - - {Template.bind({})} - - diff --git a/addons/rose/addon/components/rose/form/radio/group/index.hbs b/addons/rose/addon/components/rose/form/radio/group/index.hbs index 8e090407d..e883eb431 100644 --- a/addons/rose/addon/components/rose/form/radio/group/index.hbs +++ b/addons/rose/addon/components/rose/form/radio/group/index.hbs @@ -8,13 +8,5 @@ changed=@changed inline=@inline ) - card=(component - 'rose/form/radio/card' - name=@name - selectedValue=@selectedValue - disabled=@disabled - changed=@changed - layout=@layout - ) ) }} \ No newline at end of file diff --git a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx index a698fedda..dd0d5b106 100644 --- a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx +++ b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx @@ -75,9 +75,9 @@ export const Template = (args) => ({ -# HDS::Form::RadioCard::Group (Recommended) +# HDS::Form::RadioCard::Group -> **_NOTE:_** Replaces the deprecated Rose/Form/Radio/Group/Card. +> **_NOTE:_** Replaces the removed Rose/Form/Radio/Group/Card. ```handlebars ({ {HDSTemplate.bind({})} - -# Rose::Form::Radio::Group::Card (Deprecated) - -```handlebars - - - - - -``` - -export const RoseTemplate = (args) => ({ - template: hbs` - - - - `, - context: args, -}); - - - - {RoseTemplate.bind({})} - - diff --git a/addons/rose/addon/modifiers/code-mirror.js b/addons/rose/addon/modifiers/code-mirror.js index 077d98d25..3a0e66aab 100644 --- a/addons/rose/addon/modifiers/code-mirror.js +++ b/addons/rose/addon/modifiers/code-mirror.js @@ -9,8 +9,8 @@ import 'codemirror/addon/edit/matchbrackets'; import 'codemirror/addon/edit/closebrackets'; import 'codemirror/addon/selection/active-line'; import 'codemirror/addon/display/autorefresh'; -import 'codemirror/addon/lint/lint' -import 'codemirror/addon/lint/json-lint' +import 'codemirror/addon/lint/lint'; +import 'codemirror/addon/lint/json-lint'; // Here we define default options for the editor. // These should follow the codemirror configuration types diff --git a/addons/rose/app/styles/rose/components/form/_index.scss b/addons/rose/app/styles/rose/components/form/_index.scss index b06e535f6..73137ac8d 100644 --- a/addons/rose/app/styles/rose/components/form/_index.scss +++ b/addons/rose/app/styles/rose/components/form/_index.scss @@ -6,4 +6,3 @@ @import 'select'; @import 'radio'; @import 'fieldset'; -@import 'radio-card'; diff --git a/addons/rose/app/styles/rose/components/form/_radio-card.scss b/addons/rose/app/styles/rose/components/form/_radio-card.scss deleted file mode 100644 index 0c61fb51d..000000000 --- a/addons/rose/app/styles/rose/components/form/_radio-card.scss +++ /dev/null @@ -1,240 +0,0 @@ -@use '../../variables/sizing'; -@use '../../utilities/type'; -@use '../../variables/typography'; - -$size-xxl: sizing.rems(xxl); // 48 -$size-xl: sizing.rems(xl); // 36 -$size-l: sizing.rems(l); // 24 -$size-m: sizing.rems(m); // 16 -$size-s: sizing.rems(s); // 12 -$size-xs: sizing.rems(xs); // 8 -$size-xxs: sizing.rems(xxs); // 4 -$size-xxxs: sizing.rems(xxxs); // 2 -$size-xxxxs: sizing.rems(xxxxs); // 1 - -$radio-svg: ( - 'default': - "", - 'hover': - "", - 'focus': - "", - 'checked': - "", - 'disabled': - "", - 'disabled-checked': - "", - 'error': - "", - 'error-checked': - "", -); - -.rose-form-radio-card { - $card-wide-width: $size-m * 10; - $card-tile-width: $size-m * 7.25; - $border-width: $size-xxxs * 2; - - &.wide { - width: ($size-m) * 20; - } - - &.tile { - width: ($size-m) * 15; - - .rose-icon { - --size: #{$size-xl}; - - padding: $size-xxxs; - } - - .rose-form-radio-label { - display: flex; - flex-direction: column; - align-items: center; - height: $size-m * 9; - - &::after { - left: $card-tile-width - $border-width; - } - } - } - - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - margin: $size-m $size-xs $size-m 0; - - .radio-card-overlay { - position: absolute; - height: $size-l + $size-xxs; - background-color: var(--ui-gray-subtler-5); - width: 100%; - bottom: 0; - right: 0; - border-bottom-left-radius: $size-xxxs; - border-bottom-right-radius: $size-xxxs; - } - - .rose-form-radio-label { - @include type.type(s, bold); - - width: ($size-m) * 23; - height: 100%; - display: block; - position: relative; - text-align: left; - border: $size-xxxxs solid var(--ui-gray-subtler-2); - box-shadow: 0 $size-xxs $size-xxs - rgba(var(--stark-components), var(--opacity-2)); - border-radius: $size-xxs; - padding: $size-s $size-m ($size-xl + $size-m); - box-sizing: border-box; - - .rose-form-helper-text { - @include type.type(xs, normal); - - padding-top: $size-xs; - } - - &::after { - position: absolute; - bottom: $size-xxs + $size-xxxxs; - right: $card-wide-width - $border-width; - display: block; - width: $size-m; - height: $size-m; - transform: scale(2); - pointer-events: none; - content: ' '; - background: no-repeat 50%/50% 50%; - background-image: url($data-svg-prefix + map-get($radio-svg, 'default')); - } - } - - .rose-form-radio-field { - position: absolute; - z-index: 1; - bottom: $size-xs; - opacity: 0; - - &:not(:disabled) { - &:focus { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'focus' - )); - } - } - } - - &:hover { - ~ .rose-form-radio-label { - border: $size-xxxxs solid var(--ui-gray); - } - } - - &:checked { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'checked' - )); - } - - .radio-card-overlay { - background-color: var(--action-subtler-1h); - - .ember-application.rose-theme-light & { - background-color: var(--action-subtler-1h); - } - @media (prefers-color-scheme: dark) { - background-color: var(--action-subtler-1); - } - - .ember-application.rose-theme-dark & { - background-color: var(--action-subtler-1); - } - } - - border: $size-xxxxs solid var(--info-subtler-1); - } - } - } - - &:disabled { - ~ .rose-form-radio-label { - border: $size-xxxxs solid var(--ui-border-subtler-1); - background-color: var(--ui-gray-subtler-6); - color: var(--ui-gray-starker-1); - - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'disabled' - )); - } - } - - &:checked { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'disabled-checked' - )); - } - } - } - } - } - - &.error { - .rose-form-radio-field { - &:not(:disabled, :focus) { - ~ .rose-form-radio-label { - color: var(--ui-gray); - border: $size-xxxxs solid var(--failure); - - .radio-card-overlay { - background-color: var(--failure-subtler-2); - } - - .rose-form-helper-text.error { - color: var(--ui-gray); - } - - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'error' - )); - } - } - - &:checked { - ~ .rose-form-radio-label { - color: var(--ui-gray); - border: $size-xxxxs solid var(--failure); - - .radio-card-overlay { - background-color: var(--failure-subtler-2); - } - - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'error-checked' - )); - } - } - } - } - } - } -} diff --git a/addons/rose/package.json b/addons/rose/package.json index b96762f51..343e908f0 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -42,7 +42,7 @@ "ember-cli-htmlbars": "^6.1.0", "ember-cli-sass": "^11.0.1", "ember-composable-helpers": "^5.0.0", - "ember-focus-trap": "^0.7.0", + "ember-focus-trap": "^1.0.1", "ember-named-blocks-polyfill": "^0.2.5", "ember-radio-button": "github:yapplabs/ember-radio-button.git#b2ab65f", "ember-uuid": "^2.1.0", diff --git a/addons/rose/tests/integration/components/rose/form/radio/card-test.js b/addons/rose/tests/integration/components/rose/form/radio/card-test.js deleted file mode 100644 index 5bc55dd0e..000000000 --- a/addons/rose/tests/integration/components/rose/form/radio/card-test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import hbs from 'htmlbars-inline-precompile'; - -module('Integration | Component | rose/form/radio/card', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs``); - assert.ok(await find('input')); - assert.strictEqual(await find('input').type, 'radio'); - }); - - test('it renders in a tile format when tile layout is applied', async function (assert) { - await render(hbs``); - assert.ok(await find('.tile')); - }); - - test('it supports an icon', async function (assert) { - await render( - hbs`` - ); - assert.ok(find('svg')); - }); - - test('it is checked when @value and @selectedValue values match', async function (assert) { - await render( - hbs`` - ); - assert.true(await find('input').checked); - }); - - test('it is not disabled by default', async function (assert) { - await render(hbs``); - assert.false(await find('input').disabled); - }); - - test('it is not in error state by default', async function (assert) { - await render(hbs``); - assert.notOk(await find('.error')); - }); - - test('it is disabled when @disabled={{true}}', async function (assert) { - await render(hbs``); - assert.true(await find('input').disabled); - }); - - test('it is in error state when @error={{true}}', async function (assert) { - await render(hbs``); - assert.ok(await find('.error')); - }); -}); diff --git a/package.json b/package.json index 67cce2a4a..feb715b38 100755 --- a/package.json +++ b/package.json @@ -105,7 +105,8 @@ "async": "^2.6.4", "terser": "^5.14.2", "jpeg-js": "^0.4.4", - "got": "^11.8.5" + "got": "^11.8.5", + "loader-utils": "^2.0.4" }, "config": { "commitizen": { diff --git a/ui/admin/.eslintrc.js b/ui/admin/.eslintrc.js index 8ea8cb7b4..bd758f6a0 100644 --- a/ui/admin/.eslintrc.js +++ b/ui/admin/.eslintrc.js @@ -32,6 +32,7 @@ module.exports = { '.eslintrc.js', '.prettierrc.js', '.template-lintrc.js', + '.stylelintrc.js', 'ember-cli-build.js', 'testem.js', 'blueprints/*/index.js', diff --git a/ui/admin/.sasslintrc b/ui/admin/.sasslintrc deleted file mode 100644 index 76ed8341e..000000000 --- a/ui/admin/.sasslintrc +++ /dev/null @@ -1,8 +0,0 @@ -files: - include: 'app/styles/**/*.s+(a|c)ss' -rules: - leading-zero: 0 - mixins-before-declarations: 0 - nesting-depth: 0 - placeholder-in-extend: 0 - property-sort-order: 0 diff --git a/ui/admin/.stylelintrc.js b/ui/admin/.stylelintrc.js new file mode 100644 index 000000000..cd5887c5f --- /dev/null +++ b/ui/admin/.stylelintrc.js @@ -0,0 +1,33 @@ +'use strict'; + +module.exports = { + extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], + rules: { + // We oftentimes want to have long form properties for readability + 'declaration-block-no-redundant-longhand-properties': null, + + // This is just a personal preference unless the quotes are needed + // to escape special characters + 'function-url-quotes': null, + + // This is often limited by the DOM and we want to order our declarations by our own standard + 'no-descending-specificity': null, + + // -Webkit animation CSS for older browsers. Need a decision to remove `-webkit` syntax in + // favor of normal CSS + 'property-no-vendor-prefix': null, + 'at-rule-no-vendor-prefix': null, + + // As a team we want to stick to using kebab-case as our selector class pattern, but there are a couple + // of exceptions in which we overrride CSS from addons like CodeMirror and HDS, in which they use + // pascal case and a double-kebab-case. This regex checks for all of those 3 patterns and won't fail + // lint check, but will fail if its any other pattern. + 'selector-class-pattern': [ + '(^([a-z][a-z0-9]*)(-{1,2}[a-z0-9]+)*$)|(^CodeMirror)', + { + message: (selector) => + `Expected class selector "${selector}" to be kebab-case`, + }, + ], + }, +}; diff --git a/ui/admin/app/abilities/credential.js b/ui/admin/app/abilities/credential.js new file mode 100644 index 000000000..825341313 --- /dev/null +++ b/ui/admin/app/abilities/credential.js @@ -0,0 +1,18 @@ +import CredentialAbility from 'api/abilities/credential'; +import { inject as service } from '@ember/service'; + +export default class OverrideCredentialAbility extends CredentialAbility { + // =service + @service features; + + /** + * This override ensures that JSON credentials may be read only if the + * json-credentials feature flag is enabled. All other types are subject + * to the standard logic found in the api addon. + */ + get canRead() { + return this.features.isEnabled('json-credentials') || !this.model.isJSON + ? super.canRead + : false; + } +} diff --git a/ui/admin/app/components/form/credential-store/static/index.hbs b/ui/admin/app/components/form/credential-store/static/index.hbs index 2d44e3dd5..1dbeec0cb 100644 --- a/ui/admin/app/components/form/credential-store/static/index.hbs +++ b/ui/admin/app/components/form/credential-store/static/index.hbs @@ -70,14 +70,13 @@ {{else}} {{t 'form.type.label'}} - {{t - (concat 'resources.credential-store.types.' @model.type) - }} + + {{t (concat 'resources.credential-store.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/credential-store/vault/index.hbs b/ui/admin/app/components/form/credential-store/vault/index.hbs index ed6e1f16c..3bdf964bc 100644 --- a/ui/admin/app/components/form/credential-store/vault/index.hbs +++ b/ui/admin/app/components/form/credential-store/vault/index.hbs @@ -70,14 +70,13 @@ {{else}} {{t 'form.type.label'}} - {{t - (concat 'resources.credential-store.types.' @model.type) - }} + + {{t (concat 'resources.credential-store.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/credential/index.hbs b/ui/admin/app/components/form/credential/index.hbs index a27830b5c..6d957e1b6 100644 --- a/ui/admin/app/components/form/credential/index.hbs +++ b/ui/admin/app/components/form/credential/index.hbs @@ -2,6 +2,7 @@ {{component (concat 'form/credential/' @model.type) model=@model + types=this.credentialTypes submit=@submit cancel=@cancel changeType=@changeType diff --git a/ui/admin/app/components/form/credential/index.js b/ui/admin/app/components/form/credential/index.js new file mode 100644 index 000000000..fa281a953 --- /dev/null +++ b/ui/admin/app/components/form/credential/index.js @@ -0,0 +1,19 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { types } from 'api/models/credential'; + +export default class FormCredentialComponent extends Component { + // =services + @service features; + + // =attributes + /** + * Returns an array of available credential types the user can create + * @type {object} + */ + get credentialTypes() { + return this.features.isEnabled('json-credentials') + ? types + : types.filter((type) => type !== 'json'); + } +} diff --git a/ui/admin/app/components/form/credential/json/index.hbs b/ui/admin/app/components/form/credential/json/index.hbs new file mode 100644 index 000000000..f1f77c226 --- /dev/null +++ b/ui/admin/app/components/form/credential/json/index.hbs @@ -0,0 +1,112 @@ + + + + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + <:body> + + {{t 'form.type.label'}} + {{#each @types as |credentialType|}} + + {{t + (concat 'resources.credential.types.' credentialType) + }} + {{t + (concat 'resources.credential.help.' credentialType) + }} + + {{/each}} + + + + + + <:title>{{t 'resources.credential.form.json.label'}} + <:description>{{t 'resources.credential.form.json.help'}} + <:body> + + + + {{else}} + + {{t 'form.type.label'}} + {{t (concat 'resources.credential.help.' @model.type)}} + + + + + <:title>{{t 'resources.credential.form.json.label'}} + <:description>{{t 'resources.credential.form.json.help'}} + <:body> + + + + {{/if}} + + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs index c87c33efa..bcee4bed2 100644 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs +++ b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs @@ -12,8 +12,6 @@ @label={{t 'form.name.label'}} @error={{@model.errors.name}} @helperText={{t 'form.name.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.name}} @@ -31,8 +29,6 @@ @label={{t 'form.description.label'}} @error={{@model.errors.description}} @helperText={{t 'form.description.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.description}} @@ -44,9 +40,9 @@ {{/if}} {{#if @model.isNew}} - + {{t 'form.type.label'}} - {{#each this.credentialTypes as |credentialType|}} + {{#each @types as |credentialType|}} {{else}} {{t 'form.type.label'}} - {{t (concat 'resources.credential.types.' @model.type)}} + {{t (concat 'resources.credential.help.' @model.type)}} {{/if}} @@ -82,8 +78,6 @@ @label={{t 'resources.credential.form.username.label'}} @helperText={{t 'resources.credential.form.username.help'}} @error={{@model.errors.username}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.username}} @@ -102,8 +96,6 @@ @label={{t 'resources.credential.form.private_key.label'}} @helperText={{t 'resources.credential.form.private_key.help'}} @error={{@model.errors.private_key}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.private_key}} @@ -121,8 +113,6 @@ @label={{t 'resources.credential.form.private_key_passphrase.label'}} @helperText={{t 'resources.credential.form.private_key_passphrase.help'}} @error={{@model.errors.private_key_passphrase}} - disabled={{@model.isSaving}} - readonly={{false}} autocomplete='new-password' as |field| > diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.js b/ui/admin/app/components/form/credential/ssh_private_key/index.js deleted file mode 100644 index c2c5d5bcb..000000000 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import Component from '@glimmer/component'; -import { types } from 'api/models/credential'; - -export default class FormSSHPrivateKeyCredentialComponent extends Component { - // =attributes - /** - * @type {Array} - */ - credentialTypes = types; -} diff --git a/ui/admin/app/components/form/credential/username_password/index.hbs b/ui/admin/app/components/form/credential/username_password/index.hbs index 731f4deb9..b0979a89d 100644 --- a/ui/admin/app/components/form/credential/username_password/index.hbs +++ b/ui/admin/app/components/form/credential/username_password/index.hbs @@ -12,8 +12,6 @@ @label={{t 'form.name.label'}} @error={{@model.errors.name}} @helperText={{t 'form.name.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.name}} @@ -31,8 +29,6 @@ @label={{t 'form.description.label'}} @error={{@model.errors.description}} @helperText={{t 'form.description.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.description}} @@ -44,9 +40,9 @@ {{/if}} {{#if @model.isNew}} - + {{t 'form.type.label'}} - {{#each this.credentialTypes as |credentialType|}} + {{#each @types as |credentialType|}} {{else}} {{t 'form.type.label'}} - {{t (concat 'resources.credential.types.' @model.type)}} + {{t (concat 'resources.credential.help.' @model.type)}} {{/if}} @@ -82,8 +78,6 @@ @label={{t 'resources.credential.form.username.label'}} @helperText={{t 'resources.credential.form.username.help'}} @error={{@model.errors.username}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.username}} @@ -103,8 +97,6 @@ @label={{t 'resources.credential.form.password.label'}} @helperText={{t 'resources.credential.form.password.help'}} @error={{@model.errors.password}} - disabled={{@model.isSaving}} - readonly={{false}} autocomplete='new-password' as |field| > diff --git a/ui/admin/app/components/form/credential/username_password/index.js b/ui/admin/app/components/form/credential/username_password/index.js deleted file mode 100644 index 67d493c10..000000000 --- a/ui/admin/app/components/form/credential/username_password/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import Component from '@glimmer/component'; -import { types } from 'api/models/credential'; - -export default class FormUserNamePasswordCredentialComponent extends Component { - // =attributes - /** - * @type {Array} - */ - credentialTypes = types; -} diff --git a/ui/admin/app/components/form/field/json-secret/index.js b/ui/admin/app/components/form/field/json-secret/index.js index d70539cdb..d1e26ec09 100644 --- a/ui/admin/app/components/form/field/json-secret/index.js +++ b/ui/admin/app/components/form/field/json-secret/index.js @@ -41,4 +41,4 @@ export default class FormFieldJsonSecretComponent extends Component { enableEditing() { if (!this.args.disabled) this.editing = true; } -} \ No newline at end of file +} diff --git a/ui/admin/app/components/form/host-catalog/aws/index.hbs b/ui/admin/app/components/form/host-catalog/aws/index.hbs index 142ac44fd..601e8f1b7 100644 --- a/ui/admin/app/components/form/host-catalog/aws/index.hbs +++ b/ui/admin/app/components/form/host-catalog/aws/index.hbs @@ -44,62 +44,47 @@ {{#if @model.isNew}} - - <:title>{{t 'form.type.label'}} - <:body> - + {{t 'form.type.label'}} + {{#each this.hostCatalogTypes as |hostCatalogType|}} + - - - - - + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + + + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{t 'descriptions.choose-a-provider'}} - - - <:title>{{t 'titles.provider'}} - - - - <:title>{{t 'titles.choose-a-provider'}} - <:description>{{t 'descriptions.choose-a-provider'}} - - <:body> - - - - - - + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + + + + {{/each-in}} + {{else}} ({ ...obj, [plugin]: icons[i] }), + {} + ); + } // =actions @action diff --git a/ui/admin/app/components/form/host-catalog/azure/index.hbs b/ui/admin/app/components/form/host-catalog/azure/index.hbs index a09a3d411..807b18f84 100644 --- a/ui/admin/app/components/form/host-catalog/azure/index.hbs +++ b/ui/admin/app/components/form/host-catalog/azure/index.hbs @@ -44,67 +44,47 @@ {{#if @model.isNew}} - - <:title>{{t 'form.type.label'}} - <:body> - + {{t 'form.type.label'}} + {{#each this.hostCatalogTypes as |hostCatalogType|}} + - - - - - - - - - <:title>{{t 'titles.provider'}} - - - <:title>{{t 'titles.choose-a-provider'}} - <:description> - {{concat + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + + + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{concat (t 'descriptions.choose-a-provider') - ' ' - (t 'descriptions.choose-a-provider-azure') - }} - + }} - <:body> - - - - - - + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + + + {{/each-in}} + {{else}} + + {{#if (can 'save model' @model)}} ({ ...obj, [plugin]: icons[i] }), + {} + ); + } +} diff --git a/ui/admin/app/components/form/host-catalog/static/index.hbs b/ui/admin/app/components/form/host-catalog/static/index.hbs index 2654c6608..3c2245981 100644 --- a/ui/admin/app/components/form/host-catalog/static/index.hbs +++ b/ui/admin/app/components/form/host-catalog/static/index.hbs @@ -46,60 +46,51 @@ {{#if @model.isNew}} - - <:title>{{t 'form.type.label'}} - <:body> - + {{t 'form.type.label'}} + {{#each this.hostCatalogTypes as |hostCatalogType|}} + - - - - - + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + {{#if @model.isPlugin}} - - - <:title>{{t 'titles.provider'}} - - - <:title>{{t 'titles.choose-a-provider'}} - <:description>{{t 'descriptions.choose-a-provider'}} - <:body> - + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{t 'descriptions.choose-a-provider'}} + + {{#each-in this.mapResourceTypeWithIcon as |pluginType|}} + - - - - - + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + {{t + (concat 'resources.host-catalog.help.' pluginType) + }} + + {{/each-in}} + {{/if}} {{/if}} diff --git a/ui/admin/app/components/form/host-catalog/static/index.js b/ui/admin/app/components/form/host-catalog/static/index.js new file mode 100644 index 000000000..c8f2e50f2 --- /dev/null +++ b/ui/admin/app/components/form/host-catalog/static/index.js @@ -0,0 +1,20 @@ +import Component from '@glimmer/component'; +import { types, pluginTypes } from 'api/models/host-catalog'; + +//Note: this is a temporary solution till we have resource type helper in place +const icons = ['aws-color', 'azure-color']; + +export default class FormStaticHostCatalogAwsComponent extends Component { + // =properties + hostCatalogTypes = types; + /** + * maps resource type with icon + * @type {object} + */ + get mapResourceTypeWithIcon() { + return pluginTypes.reduce( + (obj, plugin, i) => ({ ...obj, [plugin]: icons[i] }), + {} + ); + } +} diff --git a/ui/admin/app/components/form/host-set/aws/index.hbs b/ui/admin/app/components/form/host-set/aws/index.hbs index cc131cd79..0f430dabb 100644 --- a/ui/admin/app/components/form/host-set/aws/index.hbs +++ b/ui/admin/app/components/form/host-set/aws/index.hbs @@ -53,26 +53,17 @@ @disabled={{true}} /> - - <:title>{{t 'titles.provider'}} - <:description>{{t 'descriptions.provider'}} - - <:body> - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.aws'}} + + - - <:title>{{t 'titles.provider'}} - <:description>{{t 'descriptions.provider'}} - - <:body> - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.azure'}} + + - - <:title>{{t 'titles.provider'}} - <:body> - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.aws'}} + + <:title>{{t 'form.ip_addresses.label'}} diff --git a/ui/admin/app/components/form/host/azure/index.hbs b/ui/admin/app/components/form/host/azure/index.hbs index c87c94843..348fe2788 100644 --- a/ui/admin/app/components/form/host/azure/index.hbs +++ b/ui/admin/app/components/form/host/azure/index.hbs @@ -52,25 +52,17 @@ {{/if}} - - <:title>{{t 'titles.provider'}} - <:body> - - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.azure'}} + + <:title>{{t 'form.ip_addresses.label'}} <:body> diff --git a/ui/admin/app/components/form/onboarding/choose-path/index.hbs b/ui/admin/app/components/form/onboarding/choose-path/index.hbs index 9e91d69e9..1c4a59b96 100644 --- a/ui/admin/app/components/form/onboarding/choose-path/index.hbs +++ b/ui/admin/app/components/form/onboarding/choose-path/index.hbs @@ -5,26 +5,33 @@ @onSubmit={{fn @submit this.path}} as |form| > - -
- - -
-
+ + + + {{t 'onboarding.quick-start.titles.quick-setup'}} + {{t + 'onboarding.quick-start.descriptions.quick-setup' + }} + + + {{t 'onboarding.quick-start.titles.manual'}} + {{t + 'onboarding.quick-start.descriptions.manual' + }} + + + \ No newline at end of file diff --git a/ui/admin/app/components/form/role/index.hbs b/ui/admin/app/components/form/role/index.hbs index 1a6a9a94e..a20c290f4 100644 --- a/ui/admin/app/components/form/role/index.hbs +++ b/ui/admin/app/components/form/role/index.hbs @@ -6,44 +6,47 @@ as |form| > - + {{t 'form.name.label'}} + {{t 'form.name.help'}} {{#if @model.errors.name}} - + {{#each @model.errors.name as |error|}} - {{error.message}} + {{error.message}} {{/each}} - + {{/if}} - + - + {{t 'form.description.label'}} + {{t 'form.description.help'}} {{#if @model.errors.description}} - + {{#each @model.errors.description as |error|}} - {{error.message}} + {{error.message}} {{/each}} - + {{/if}} - + {{#unless @model.isNew}}
{{#if (and @model.isNew (feature-flag 'ssh-target'))}} - - {{t 'form.type.label'}} - {{#each-in this.mapResourceTypeWithIcon as |targetType icon|}} + + {{t 'resources.target.form.type.label'}} + {{t 'resources.target.form.type.help'}} + {{#each this.typeMetas as |type|}} - {{t (concat 'resources.target.types.' targetType)}} + {{t (concat 'resources.target.types.' type.type)}} {{t - (concat 'resources.target.help.' targetType) + (concat 'resources.target.help.' type.type) }} - + - {{/each-in}} + {{/each}} - +
{{else}} {{t 'form.type.label'}} - {{t (concat 'resources.target.types.' @model.type)}} + + {{t (concat 'resources.target.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/target/details/index.js b/ui/admin/app/components/form/target/details/index.js index d37de8a08..0430fcd80 100644 --- a/ui/admin/app/components/form/target/details/index.js +++ b/ui/admin/app/components/form/target/details/index.js @@ -1,8 +1,12 @@ import Component from '@glimmer/component'; -import { types } from 'api/models/target'; +import { TYPES_TARGET } from 'api/models/target'; -//Note: this is a temporary solution till we have resource type helper in place -const icons = ['network', 'terminal']; +// NOTE: this is all a temporary solution till we have a resource type helper. +const types = [...TYPES_TARGET].reverse(); +const icons = { + ssh: 'terminal-screen', + tcp: 'network', +}; export default class FormTargetComponent extends Component { // =properties @@ -10,8 +14,11 @@ export default class FormTargetComponent extends Component { * maps resource type with icon * @type {object} */ - get mapResourceTypeWithIcon() { - return types.reduce((obj, type, i) => ({ ...obj, [type]: icons[i] }), {}); + get typeMetas() { + return types.map((type) => ({ + type, + icon: icons[type], + })); } /** @@ -20,10 +27,6 @@ export default class FormTargetComponent extends Component { * @type {string} */ get icon() { - if (this.args.model.type === 'tcp') { - return icons[0]; - } else { - return icons[1]; - } + return icons[this.args.model.type]; } } diff --git a/ui/admin/app/components/form/worker/create-worker-led/index.js b/ui/admin/app/components/form/worker/create-worker-led/index.js index b1c804d60..24afe932f 100644 --- a/ui/admin/app/components/form/worker/create-worker-led/index.js +++ b/ui/admin/app/components/form/worker/create-worker-led/index.js @@ -108,10 +108,19 @@ ${listenerText} * @type {string} */ get installBoundaryText() { - return `curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - ;\\ + const configPath = this.configFilePath || ''; + const ossContent = `curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - ;\\ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" ;\\ sudo apt-get update && sudo apt-get install boundary ;\\ -boundary server -config="${this.configFilePath || ''}/pki-worker.hcl"`; +boundary server -config="${configPath}/pki-worker.hcl"`; + const hcpContent = `wget -q https://releases.hashicorp.com/boundary-worker/0.11.2+hcp/boundary-worker_0.11.2+hcp_linux_amd64.zip ;\\ +sudo apt-get update && sudo apt-get install unzip ;\\ +unzip *.zip ;\\ +./boundary-worker server -config="${configPath}/pki-worker.hcl"`; + + return this.features.isEnabled('byow-pki-hcp-cluster-id') + ? hcpContent + : ossContent; } /** diff --git a/ui/admin/app/routes/onboarding/quick-setup/create-resources.js b/ui/admin/app/routes/onboarding/quick-setup/create-resources.js index d55239c7d..cc9f540ea 100644 --- a/ui/admin/app/routes/onboarding/quick-setup/create-resources.js +++ b/ui/admin/app/routes/onboarding/quick-setup/create-resources.js @@ -3,6 +3,7 @@ import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { loading } from 'ember-loading'; import { notifyError } from 'core/decorators/notify'; +import { TYPE_TARGET_TCP } from 'api/models/target'; export default class OnboardingQuickSetupCreateResourcesRoute extends Route { // =services @@ -40,7 +41,7 @@ export default class OnboardingQuickSetupCreateResourcesRoute extends Route { description: 'Sample host created by quick setup', }), target: this.store.createRecord('target', { - type: 'tcp', + type: TYPE_TARGET_TCP, name: 'EC2 Instances', description: 'Sample target created by quick setup', }), diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js index 28c7b2019..65e8c3577 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js @@ -29,15 +29,23 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRou 'scopes.scope.credential-stores.credential-store' ); let name, description; + /** + * this sets the credential attribute json_object to a string that + * replicates an empty json blob as CodeMirror expects a string + */ + const json_object = '{}'; + if (this.currentModel?.isNew) { ({ name, description } = this.currentModel); this.currentModel.rollbackAttributes(); } + return this.store.createRecord('credential', { type, credential_store_id, name, description, + json_object, }); } diff --git a/ui/admin/app/routes/scopes/scope/roles/role/principals.js b/ui/admin/app/routes/scopes/scope/roles/role/principals.js index 25b9cbb64..4977dac20 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/principals.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/principals.js @@ -8,60 +8,32 @@ import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeRolesRolePrincipalsRoute extends Route { // =services @service intl; - @service resourceFilterStore; // =methods /** - * Returns users and groups associated with this role. + * Returns users, groups and managed-groups associated with this role. * @param {object} params - * @return {Promise{RoleModel}} + * @return {Promise{role, principals}} */ async model() { const role = this.modelFor('scopes.scope.roles.role'); + // Fetch user and group principals. + const users = await role.users; + const groups = await role.groups; + const managedGroups = await role.managedGroups; + // Merge polymorphic principals. + const principals = [...users, ...groups, ...managedGroups]; - // Gather user and group IDs as separate arrays, since these - // will be queried in separate API queries. - const userIDs = role.principals - .filter(({ type }) => type === 'user') - .map(({ principal_id }) => principal_id); - const groupIDs = role.principals - .filter(({ type }) => type === 'group') - .map(({ principal_id }) => principal_id); - - // Query for users. - const users = userIDs?.length - ? ( - await this.resourceFilterStore.queryBy( - 'user', - { id: userIDs }, - { scope_id: 'global', recursive: true } - ) - ).map((model) => model) - : []; - // Query for groups. - const groups = groupIDs?.length - ? ( - await this.resourceFilterStore.queryBy( - 'group', - { id: groupIDs }, - { scope_id: 'global', recursive: true } - ) - ).map((model) => model) - : []; - - return { - role, - principals: users.concat(groups), - }; + return { role, principals }; } // =actions /** * Remove a principal from the current role and redirect to principals index. - * @param {UserModel, GroupModel} principal + * @param {UserModel, GroupModel, ManagedGroupModel} principal */ @action @loading diff --git a/ui/admin/app/routes/scopes/scope/targets/new.js b/ui/admin/app/routes/scopes/scope/targets/new.js index 7aae3db3d..d43e0cc4e 100644 --- a/ui/admin/app/routes/scopes/scope/targets/new.js +++ b/ui/admin/app/routes/scopes/scope/targets/new.js @@ -1,12 +1,14 @@ import Route from '@ember/routing/route'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; export default class ScopesScopeTargetsNewRoute extends Route { // =services @service store; @service router; + @service features; // =methods @@ -23,9 +25,16 @@ export default class ScopesScopeTargetsNewRoute extends Route { * @return {TargetModel} */ - model({ type = 'tcp' }) { - const scopeModel = this.modelFor('scopes.scope'); + model({ type }) { let name, description; + const scopeModel = this.modelFor('scopes.scope'); + + // default type is SSH if the feature is enabled, otherwise TCP + if (!type) + type = this.features.isEnabled('ssh-target') + ? TYPE_TARGET_SSH + : TYPE_TARGET_TCP; + if (this.currentModel?.isNew) { ({ name, description } = this.currentModel); this.currentModel.rollbackAttributes(); diff --git a/ui/admin/app/styles/_notify.scss b/ui/admin/app/styles/_notify.scss index 26ef1675a..355e7bdc7 100644 --- a/ui/admin/app/styles/_notify.scss +++ b/ui/admin/app/styles/_notify.scss @@ -1,7 +1,6 @@ // This file is essentially a rip of the default ember notify styles with some minor tweaks for positioning. -// sass-lint:disable no-vendor-prefixes -@-webkit-keyframes 'ember-notify-show' { +@-webkit-keyframes ember-notify-show { from { left: -310px; } @@ -10,7 +9,7 @@ left: 0; } } -@keyframes 'ember-notify-show' { +@keyframes ember-notify-show { from { left: -310px; } @@ -19,7 +18,7 @@ left: 0; } } -@-webkit-keyframes 'ember-notify-hide' { +@-webkit-keyframes ember-notify-hide { from { left: 0; } @@ -28,7 +27,7 @@ left: -310px; } } -@keyframes 'ember-notify-hide' { +@keyframes ember-notify-hide { from { left: 0; } diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 9f3fd2b00..849259a2c 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -1,4 +1,3 @@ -// sass-lint:disable no-color-literals class-name-format @use 'rose/variables/sizing'; @use 'rose/variables/media'; @use 'rose/utilities/type'; @@ -194,11 +193,11 @@ } mark { - animation: fadeBackground 0.75s linear; + animation: fade-background 0.75s linear; background-color: transparent; color: inherit; - @keyframes fadeBackground { + @keyframes fade-background { 0% { background-color: var(--action-subtler-1); } @@ -222,7 +221,7 @@ white-space: nowrap; } -// Spinning animation for refresh button icon +// Spinning animation for refresh button icon @media (prefers-reduced-motion: no-preference) { .is-loading { .rose-icon { @@ -239,7 +238,7 @@ } } -//onboarding quick setup styles +// onboarding quick setup styles .choose-path { display: block; width: sizing.rems(xl) * 15; @@ -416,7 +415,7 @@ .worker-auth-token { align-items: flex-end; display: flex; - gap: sizing.rems(m); // sass-lint:disable-line no-misspelled-properties + gap: sizing.rems(m); } .worker-success { @@ -427,7 +426,7 @@ > div { align-items: center; display: flex; - gap: sizing.rems(xs); // sass-lint:disable-line no-misspelled-properties + gap: sizing.rems(xs); p { margin-bottom: sizing.rems(xxs); @@ -458,7 +457,7 @@ .info-field { .hds-form-field--layout-vertical { - grid-template-areas: 'label' 'helper-text' 'text-input'; + grid-template-areas: 'label' 'text-input' 'helper-text'; .hds-form-label { font-size: sizing.rems(m); @@ -466,26 +465,25 @@ line-height: sizing.rems(m) + sizing.rems(xxs); } - .hds-form-helper-text { - color: var(--token-form-label-color); - font-size: sizing.rems(m); - grid-area: helper-text; - } - .hds-form-text-input { background-color: transparent; border: 0; + color: var(--token-form-label-color); grid-area: text-input; - line-height: sizing.rems(m) + sizing.rems(xxs); - margin-top: sizing.rems(xs); + font-size: sizing.rems(m); padding: 0; } + + .hds-form-helper-text { + color: var(--token-form-label-color); + grid-area: helper-text; + } } &.has-icon { .flight-icon { position: absolute; - transform: translatey(sizing.rems(xl) - sizing.rems(s)); + transform: translateY(sizing.rems(xl) - sizing.rems(xs)); } .hds-form-helper-text, @@ -508,28 +506,25 @@ border-radius: sizing.rems(xxs); height: var(--secret-editor-height); margin-bottom: sizing.rems(l); + width: 100%; .secret-editor-json { .CodeMirror { border-radius: sizing.rems(xxs); height: var(--secret-editor-height); + display: block; } .CodeMirror-gutters { background: var(--secret-editor-gutter); } - - // override CodeMirror padding to add padding to left of line numbers - .CodeMirror-linenumber { - padding: 0; - } } .secret-editor-skeleton-wrapper { // using ui-gray directly so it does not change when // user is in dark mode since the secret editor // does not change theme colors between light and dark - color: #{color(ui-gray)}; + color: var(--token-color-palette-neutral-300); display: flex; height: 100%; position: relative; @@ -581,6 +576,7 @@ left: 50%; position: absolute; top: 50%; + // centers message with editor body and gutter transform: translate(-50%, -50%); z-index: 1; @@ -591,6 +587,7 @@ border: sizing.rems(xxxxs) solid var(--ui-gray); border-radius: sizing.rems(xxxs); box-shadow: 0 sizing.rems(xxs) sizing.rems(xxs) var(--button-shadow); + // using ui-gray directly so it does not change when // user is in dark mode since the secret editor // does not change theme colors between light and dark diff --git a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs index e80fab03d..29a1c405c 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs @@ -37,7 +37,7 @@ }} - {{#if brokeredCredentialSource.isUnknown}} + {{#if (cannot 'read credential' brokeredCredentialSource)}} {{brokeredCredentialSource.displayName}} {{else}} {{#if brokeredCredentialSource.isStatic}} diff --git a/ui/admin/package.json b/ui/admin/package.json index 12f60b47e..96d9cbc8d 100644 --- a/ui/admin/package.json +++ b/ui/admin/package.json @@ -21,7 +21,8 @@ "lint:hbs": "ember-template-lint .", "lint:hbs:fix": "ember-template-lint . --fix", "lint:js": "eslint .", - "lint:sass": "sass-lint -v -q", + "lint:sass": "stylelint 'app/styles/**/*.scss'", + "lint:sass:fix": "stylelint 'app/styles/**/*.scss' --fix", "format": "npm-run-all format:*", "format:hbs": "prettier --write 'app/**/*.hbs'", "format:js": "prettier --write '{app,config,stories,tests}/**/*.js' *.js", @@ -91,8 +92,9 @@ "qunit": "^2.17.2", "qunit-dom": "^2.0.0", "rose": "*", - "sass": "^1.55.0", - "sass-lint": "^1.13.1", + "stylelint": "^14.14.0", + "stylelint-config-prettier-scss": "^0.0.1", + "stylelint-config-standard-scss": "^6.0.0", "sinon": "^14.0.0", "webpack": "^5" }, diff --git a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js index 298ee7c1e..ce7f2040d 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; +import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -14,6 +14,8 @@ module( let getCredentialsCount; let getUsernamePasswordCredentialCount; let getUsernameKeyPairCredentialCount; + let getJsonCredentialCount; + let featuresService; const instances = { scopes: { @@ -33,6 +35,7 @@ module( hooks.beforeEach(function () { // Generate resources + featuresService = this.owner.lookup('service:features'); instances.scopes.org = this.server.create('scope', { type: 'org', scope: { id: 'global', type: 'global' }, @@ -50,7 +53,7 @@ module( urls.credentialStores = `${urls.projectScope}/credential-stores`; urls.staticCredentialStore = `${urls.credentialStores}/${instances.staticCredentialStore.id}`; urls.credentials = `${urls.staticCredentialStore}/credentials`; - urls.newCredential = `${urls.staticCredentialStore}/credentials/new`; + urls.newCredential = `${urls.credentials}/new`; // Generate resource counter getCredentialsCount = () => { return this.server.schema.credentials.all().models.length; @@ -64,17 +67,23 @@ module( return this.server.schema.credentials.where({ type: 'ssh_private_key' }) .length; }; + getJsonCredentialCount = () => { + return this.server.schema.credentials.where({ type: 'json' }).length; + }; authenticateSession({}); }); - test('Users can create a new username & password credential', async function (assert) { + test('users can create a new username & password credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); const usernamePasswordCredentialCount = getUsernamePasswordCredentialCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); assert.strictEqual( getUsernamePasswordCredentialCount(), @@ -82,15 +91,18 @@ module( ); }); - test('Users can create a new username & key pair credential', async function (assert) { + test('users can create a new username & key pair credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); const usernameKeyPairCredentialCount = getUsernameKeyPairCredentialCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[value="ssh_private_key"]'); await click('[type="submit"]'); + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); assert.strictEqual( getUsernameKeyPairCredentialCount(), @@ -98,42 +110,85 @@ module( ); }); - test('Users can cancel create new username & password credential', async function (assert) { + test('users can create a new json credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); - await visit(urls.newCredential); + const jsonCredentialCount = getJsonCredentialCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + await fillIn('[name="name"]', 'random string'); + await click('[value="json"]'); + await click('[type="submit"]'); + + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); + assert.strictEqual(getJsonCredentialCount(), jsonCredentialCount + 1); + }); + + test('users can cancel create new username & password credential', async function (assert) { + assert.expect(2); + const credentialsCount = getCredentialsCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual(getCredentialsCount(), credentialsCount); }); - test('Users can cancel create new username & key pair credential', async function (assert) { + test('users can cancel create new username & key pair credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[value="ssh_private_key"]'); await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual(getCredentialsCount(), credentialsCount); }); - test('Users cannot navigate to new credential route without proper authorization', async function (assert) { + test('users can cancel create new json credential', async function (assert) { + assert.expect(2); + const credentialsCount = getCredentialsCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + await fillIn('[name="name"]', 'random string'); + await click('[value="json"]'); + await click('.rose-form-actions [type="button"]'); + + assert.strictEqual(currentURL(), urls.credentials); + assert.strictEqual(getCredentialsCount(), credentialsCount); + }); + + test('users cannot navigate to new credential route without proper authorization', async function (assert) { assert.expect(2); instances.staticCredentialStore.authorized_collection_actions.credentials = - []; - await visit(urls.staticCredentialStore); - assert.notOk( + instances.staticCredentialStore.authorized_collection_actions.credentials.filter( + (item) => { + item !== 'create'; + } + ); + await visit(urls.credentialStores); + + await click(`[href="${urls.staticCredentialStore}"]`); + + assert.false( instances.staticCredentialStore.authorized_collection_actions.credentials.includes( 'create' ) ); - assert.notOk(find(`[href="${urls.newCredential}"]`)); + assert.dom('.rose-layout-page-actions a').doesNotExist(); }); test('saving a new username & password credential with invalid fields displays error messages', async function (assert) { assert.expect(2); + await visit(urls.credentials); this.server.post('/credentials', () => { return new Response( 400, @@ -154,20 +209,21 @@ module( } ); }); - await visit(urls.newCredential); + + await click(`[href="${urls.newCredential}"]`); await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Error in provided request.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Field required for creating a username-password credential.' - ); + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + assert + .dom('.rose-form-error-message') + .hasText('Field required for creating a username-password credential.'); }); test('saving a new username & key pair credential with invalid fields displays error messages', async function (assert) { assert.expect(2); + await visit(urls.credentials); this.server.post('/credentials', () => { return new Response( 400, @@ -188,17 +244,70 @@ module( } ); }); - await visit(urls.newCredential); + + await click(`[href="${urls.newCredential}"]`); await click('[value="ssh_private_key"]'); await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Error in provided request.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Field required for creating a username-key-pair credential.' + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + assert + .dom('.rose-form-error-message') + .hasText('Field required for creating a username-key-pair credential.'); + }); + + test('saving a new json credential with invalid fields displays error messages', async function (assert) { + assert.expect(1); + await visit(urls.credentials); + this.server.post('/credentials', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'Error in provided request.', + details: { + request_fields: [ + { + name: 'attributes.json_object', + description: 'Field required for creating a json credential.', + }, + ], + }, + } + ); + }); + + await click(`[href="${urls.newCredential}"]`); + await click('[value="json"]'); + await click('[type="submit"]'); + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + }); + + test('cannot navigate to json credential when feature is disabled', async function (assert) { + featuresService.disable('json-credentials'); + assert.expect(4); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + + assert.true( + instances.staticCredentialStore.authorized_collection_actions.credentials.includes( + 'create' + ) ); + assert.dom('.hds-form-radio-card').exists({ count: 2 }); + assert + .dom('.hds-form-radio-card:first-child input') + .hasAttribute('value', 'username_password'); + assert + .dom('.hds-form-radio-card:nth-child(2) input') + .hasAttribute('value', 'ssh_private_key'); }); } ); diff --git a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js index 57192d5f0..108cc7db1 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js @@ -13,6 +13,7 @@ module( let getUsernamePasswordCredentialCount; let getUsernameKeyPairCredentialCount; + let getJSONCredentialCount; const instances = { scopes: { @@ -28,6 +29,7 @@ module( credentials: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; hooks.beforeEach(function () { @@ -54,6 +56,11 @@ module( credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -61,6 +68,7 @@ module( urls.credentials = `${urls.staticCredentialStore}/credentials`; urls.usernamePasswordCredential = `${urls.credentials}/${instances.usernamePasswordCredential.id}`; urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`; + urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; // Generate resource counter getUsernamePasswordCredentialCount = () => { return this.server.schema.credentials.where({ @@ -71,6 +79,9 @@ module( return this.server.schema.credentials.where({ type: 'ssh_private_key' }) .length; }; + getJSONCredentialCount = () => { + return this.server.schema.credentials.where({ type: 'json' }).length; + }; authenticateSession({}); }); @@ -100,6 +111,15 @@ module( ); }); + test('can delete JSON credential', async function (assert) { + assert.expect(2); + const jsonCredentialCount = getJSONCredentialCount(); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.strictEqual(currentURL(), urls.credentials); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount - 1); + }); + test('cannot delete a username & password credential without proper authorization', async function (assert) { assert.expect(3); const usernamePasswordCredentialCount = @@ -138,6 +158,21 @@ module( ); }); + test('cannot delete a JSON credential without proper authorization', async function (assert) { + assert.expect(3); + const jsonCredentialCount = getJSONCredentialCount(); + instances.jsonCredential.authorized_actions = + instances.jsonCredential.authorized_actions.filter( + (item) => item !== 'delete' + ); + await visit(urls.jsonCredential); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert + .dom('.rose-layout-page-actions .rose-dropdown-button-danger') + .doesNotExist(); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount); + }); + test('can accept delete username & password credential via dialog', async function (assert) { assert.expect(2); const confirmService = this.owner.lookup('service:confirm'); @@ -170,6 +205,18 @@ module( ); }); + test('can accept delete JSON credential via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + const jsonCredentialCount = getJSONCredentialCount(); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + await click('.rose-dialog footer .rose-button-primary'); + assert.strictEqual(currentURL(), urls.credentials); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount - 1); + }); + test('can cancel delete username & password credential via dialog', async function (assert) { assert.expect(2); const confirmService = this.owner.lookup('service:confirm'); @@ -202,6 +249,18 @@ module( ); }); + test('can cancel delete JSON credential via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + const jsonCredentialCount = getJSONCredentialCount(); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + await click('.rose-dialog footer .rose-button-secondary'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount); + }); + test('deleting a username & password credential which errors displays error message', async function (assert) { assert.expect(1); this.server.del('/credentials/:id', () => { @@ -237,5 +296,23 @@ module( await click('.rose-layout-page-actions .rose-dropdown-button-danger'); assert.ok(find('[role="alert"]').textContent.trim(), 'Oops.'); }); + + test('deleting a JSON credential which errors displays error message', async function (assert) { + assert.expect(1); + this.server.del('/credentials/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]').textContent.trim(), 'Oops.'); + }); } ); diff --git a/ui/admin/tests/acceptance/credential-store/credentials/list-test.js b/ui/admin/tests/acceptance/credential-store/credentials/list-test.js index 84e10e605..61ae810e9 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/list-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/list-test.js @@ -16,6 +16,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { staticCredentialStore: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; const urls = { @@ -50,6 +51,11 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -75,7 +81,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { .isVisible({ count: this.server.schema.credentials.all().models.length }); }); - test('User cannot navigate to index without either list or create action', async function (assert) { + test('User cannot navigate to Credentials tab without either list or create action', async function (assert) { assert.expect(3); instances.staticCredentialStore.authorized_collection_actions.credentials = []; @@ -93,7 +99,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { assert.dom('.rose-nav-tabs a:nth-child(2)').doesNotExist(); }); - test('User can navigate to index with only create action', async function (assert) { + test('User can navigate to new credential screen with only create action', async function (assert) { assert.expect(2); instances.staticCredentialStore.authorized_collection_actions.credentials = ['create']; diff --git a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js index ff67baa3d..2e8f482ed 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js @@ -17,6 +17,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { staticCredentialStore: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; const urls = { @@ -26,6 +27,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { credentials: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, unknownCredential: null, }; @@ -53,6 +55,11 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -60,6 +67,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { urls.credentials = `${urls.staticCredentialStore}/credentials`; urls.usernamePasswordCredential = `${urls.credentials}/${instances.usernamePasswordCredential.id}`; urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`; + urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; urls.unknownCredential = `${urls.credentials}/foo`; authenticateSession({}); }); @@ -86,6 +94,17 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { assert.strictEqual(currentURL(), urls.usernameKeyPairCredential); }); + test('visiting JSON credential', async function (assert) { + assert.expect(2); + await visit(urls.staticCredentialStore); + await click(`[href="${urls.credentials}"]`); + await a11yAudit(); + assert.strictEqual(currentURL(), urls.credentials); + await click(`[href="${urls.jsonCredential}"]`); + await a11yAudit(); + assert.strictEqual(currentURL(), urls.jsonCredential); + }); + test('cannot navigate to a username & password credential form without proper authorization', async function (assert) { assert.expect(1); instances.usernamePasswordCredential.authorized_actions = @@ -106,6 +125,24 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { assert.dom('.rose-table-body tr:nth-child(2) a').doesNotExist(); }); + test('cannot navigate to a JSON credential form without proper authorization', async function (assert) { + assert.expect(1); + instances.jsonCredential.authorized_actions = + instances.jsonCredential.authorized_actions.filter( + (item) => item != 'read' + ); + await visit(urls.credentials); + assert.dom('.rose-table-body tr:nth-child(3) a').doesNotExist(); + }); + + test('cannot navigate to a JSON credential form when feature not enabled', async function (assert) { + assert.expect(1); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('json-credentials'); + await visit(urls.credentials); + assert.dom('.rose-table-body tr:nth-child(3) a').doesNotExist(); + }); + test('visiting an unknown credential displays 404 message', async function (assert) { assert.expect(1); await visit(urls.unknownCredential); diff --git a/ui/admin/tests/acceptance/credential-store/credentials/update-test.js b/ui/admin/tests/acceptance/credential-store/credentials/update-test.js index 9e70f5594..2e30e8702 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/update-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/update-test.js @@ -1,5 +1,12 @@ import { module, test } from 'qunit'; -import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; +import { + visit, + currentURL, + find, + click, + fillIn, + waitUntil, +} from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; @@ -20,6 +27,7 @@ module( staticCredentialStore: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; const urls = { @@ -29,6 +37,7 @@ module( credentials: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; hooks.beforeEach(function () { @@ -56,6 +65,11 @@ module( credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -63,6 +77,7 @@ module( urls.credentials = `${urls.staticCredentialStore}/credentials`; urls.usernamePasswordCredential = `${urls.credentials}/${instances.usernamePasswordCredential.id}`; urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`; + urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; authenticateSession({}); }); @@ -98,6 +113,21 @@ module( ); }); + test('can save changes to existing JSON credential', async function (assert) { + assert.expect(3); + const mockInput = 'random string'; + assert.notEqual(instances.jsonCredential.name, mockInput); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + await click('.rose-form-actions [type="submit"]'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.strictEqual( + this.server.schema.credentials.where({ type: 'json' }).models[0].name, + mockInput + ); + }); + test('cannot make changes to an existing username & password credential without proper authorization', async function (assert) { assert.expect(1); instances.usernamePasswordCredential.authorized_actions = @@ -122,6 +152,18 @@ module( .doesNotExist(); }); + test('cannot make changes to an existing JSON credential without proper authorization', async function (assert) { + assert.expect(1); + instances.jsonCredential.authorized_actions = + instances.jsonCredential.authorized_actions.filter( + (item) => item !== 'update' + ); + await visit(urls.jsonCredential); + assert + .dom('.rose-layout-page-actions .rose-button-secondary') + .doesNotExist(); + }); + test('can cancel changes to existing username & password credential', async function (assert) { assert.expect(2); const mockInput = 'random string'; @@ -150,6 +192,20 @@ module( ); }); + test('can cancel changes to existing JSON credential', async function (assert) { + assert.expect(2); + const mockInput = 'random string'; + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + await click('.rose-form-actions [type="button"]'); + assert.notEqual(instances.jsonCredential.name, mockInput); + assert.strictEqual( + find('[name="name"]').value, + instances.jsonCredential.name + ); + }); + test('saving an existing username & password credential with invalid fields displays error message', async function (assert) { assert.expect(2); const mockInput = 'random string'; @@ -222,6 +278,42 @@ module( ); }); + test('saving an existing JSON credential with invalid fields displays error message', async function (assert) { + assert.expect(2); + const mockInput = 'random string'; + this.server.patch('/credentials/:id', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'Error in provided request.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + await click('[type="submit"]'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'Error in provided request.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.' + ); + }); + test('can discard unsaved username & password credential changes via dialog', async function (assert) { assert.expect(5); const mockInput = 'random string'; @@ -236,7 +328,10 @@ module( await visit(urls.credentials); } catch (e) { assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + await click( + '.rose-dialog-footer button:first-of-type', + 'Click Discard' + ); assert.strictEqual(currentURL(), urls.credentials); assert.notEqual( this.server.schema.credentials.where({ type: 'username_password' }) @@ -260,7 +355,10 @@ module( await visit(urls.credentials); } catch (e) { assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + await click( + '.rose-dialog-footer button:first-of-type', + 'Click Discard' + ); assert.strictEqual(currentURL(), urls.credentials); assert.notEqual( this.server.schema.credentials.where({ type: 'ssh_private_key' }) @@ -270,6 +368,32 @@ module( } }); + test('can discard unsaved JSON credential changes via dialog', async function (assert) { + assert.expect(5); + const mockInput = 'random string'; + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.jsonCredential.name, mockInput); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + assert.strictEqual(currentURL(), urls.jsonCredential); + try { + await visit(urls.credentials); + } catch (e) { + assert.dom('.rose-dialog').isVisible(); + await click( + '.rose-dialog-footer button:first-of-type', + 'Click Discard' + ); + assert.strictEqual(currentURL(), urls.credentials); + assert.notEqual( + this.server.schema.credentials.where({ type: 'json' }).models[0].name, + mockInput + ); + } + }); + test('can cancel discard unsaved username & password credential changes via dialog', async function (assert) { assert.expect(6); const mockInput = 'random string'; @@ -322,6 +446,31 @@ module( } }); + test('can cancel discard unsaved JSON credential changes via dialog', async function (assert) { + assert.expect(6); + const mockInput = 'random string'; + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.jsonCredential.name, mockInput); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + const credentialName = find('[name="name"]').value; + await fillIn('[name="name"]', mockInput); + assert.strictEqual(currentURL(), urls.jsonCredential); + try { + await visit(urls.credentials); + } catch (e) { + assert.dom('.rose-dialog').isVisible(); + await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.strictEqual(find('[name="name"]').value, mockInput); + assert.strictEqual( + this.server.schema.credentials.where({ type: 'json' }).models[0].name, + credentialName + ); + } + }); + test('password field renders in edit mode only for a username & password credential', async function (assert) { assert.expect(3); await visit(urls.usernamePasswordCredential); @@ -341,5 +490,28 @@ module( assert.dom('[name="private_key"]').isVisible(); assert.dom('[name="private_key_passphrase"]').isVisible(); }); + + test('secret editor is in actionable state when entering edit mode of a JSON credential', async function (assert) { + assert.expect(3); + await visit(urls.jsonCredential); + assert.dom('.secret-editor-skeleton-message button').doesNotExist(); + await click('form [type="button"]', 'Activate edit mode'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.dom('.secret-editor-skeleton-message button').isVisible(); + }); + + test('secret editor enters editing state when clicking edit button in the secret editor of a JSON credential', async function (assert) { + assert.expect(4); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.dom('.secret-editor-skeleton-message button').isVisible(); + await click( + '.secret-editor-skeleton-message button', + 'Enter editing mode' + ); + assert.dom('.secret-editor-skeleton-message button').doesNotExist(); + await waitUntil(() => assert.dom('.CodeMirror').isVisible()); + }); } ); diff --git a/ui/admin/tests/acceptance/host-catalogs/create-test.js b/ui/admin/tests/acceptance/host-catalogs/create-test.js index e189f5488..4e83f59ab 100644 --- a/ui/admin/tests/acceptance/host-catalogs/create-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/create-test.js @@ -32,7 +32,8 @@ module('Acceptance | host-catalogs | create', function (hooks) { hostCatalog: null, newHostCatalog: null, newStaticHostCatalog: null, - newDynamicHostCatalog: null, + newAWSDynamicHostCatalog: null, + newAzureDynamicHostCatalog: null, }; hooks.beforeEach(function () { @@ -67,7 +68,8 @@ module('Acceptance | host-catalogs | create', function (hooks) { urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; urls.newHostCatalog = `${urls.hostCatalogs}/new`; urls.newStaticHostCatalog = `${urls.newHostCatalog}?type=static`; - urls.newDynamicHostCatalog = `${urls.newHostCatalog}?type=aws`; + urls.newAWSDynamicHostCatalog = `${urls.newHostCatalog}?type=aws`; + urls.newAzureDynamicHostCatalog = `${urls.newHostCatalog}?type=azure`; // Generate resource couner gethostCatalogCount = () => this.server.schema.hostCatalogs.all().models.length; @@ -81,18 +83,29 @@ module('Acceptance | host-catalogs | create', function (hooks) { await visit(urls.newStaticHostCatalog); await fillIn('[name="name"]', 'random string'); await fillIn('[name="description"]', 'random string'); - await fillIn('[name="types"]', 'static'); + await fillIn('[name="type"]', 'static'); await click('[type="submit"]'); assert.strictEqual(gethostCatalogCount(), count + 1); }); - test('Users can create new dynamic host catalogs', async function (assert) { + test('Users can create new dynamic aws host catalogs with aws provider', async function (assert) { assert.expect(1); const count = gethostCatalogCount(); - await visit(urls.newDynamicHostCatalog); + await visit(urls.newAWSDynamicHostCatalog); await fillIn('[name="name"]', 'random string'); await fillIn('[name="description"]', 'random string'); - await fillIn('[name="types"]', 'aws'); + await fillIn('[name="type"]', 'aws'); + await click('[type="submit"]'); + assert.strictEqual(gethostCatalogCount(), count + 1); + }); + + test('Users can create new dynamic aws host catalogs with azure provider', async function (assert) { + assert.expect(1); + const count = gethostCatalogCount(); + await visit(urls.newAzureDynamicHostCatalog); + await fillIn('[name="name"]', 'random string'); + await fillIn('[name="description"]', 'random string'); + await fillIn('[name="type"]', 'azure'); await click('[type="submit"]'); assert.strictEqual(gethostCatalogCount(), count + 1); }); @@ -107,10 +120,20 @@ module('Acceptance | host-catalogs | create', function (hooks) { assert.strictEqual(gethostCatalogCount(), count); }); - test('Users can cancel creation of new dynamic host catalogs', async function (assert) { + test('Users can cancel creation of new dynamic host catalogs with AWS provider', async function (assert) { assert.expect(2); const count = gethostCatalogCount(); - await visit(urls.newDynamicHostCatalog); + await visit(urls.newAWSDynamicHostCatalog); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.hostCatalogs); + assert.strictEqual(gethostCatalogCount(), count); + }); + + test('Users can cancel creation of new dynamic host catalogs with Azure provider', async function (assert) { + assert.expect(2); + const count = gethostCatalogCount(); + await visit(urls.newAzureDynamicHostCatalog); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="button"]'); assert.strictEqual(currentURL(), urls.hostCatalogs); diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js index 6aa8fe830..cc0c8891e 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js @@ -97,9 +97,9 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) { const name = 'aws host set'; await fillIn('[name="name"]', name); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); await fillIn('[name="filters"]', 'filter'); - await click('form fieldset:nth-of-type(3) [title="Add"]'); + await click('form fieldset:nth-of-type(2) [title="Add"]'); await fillIn('[name="sync_interval_seconds"]', 10); await click('[type="submit"]'); @@ -128,7 +128,7 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) { const name = 'azure host set'; await fillIn('[name="name"]', name); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); await fillIn('[name="filter"]', 'filter'); await fillIn('[name="sync_interval_seconds"]', 10); await click('[type="submit"]'); diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js index 46864ff1c..669ae7425 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js @@ -147,20 +147,20 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { await fillIn('[name="name"]', name); // Remove all the preferred endpoints await Promise.all( - findAll('form fieldset:nth-of-type(2) [title="Remove"]').map((element) => + findAll('form fieldset:nth-of-type(1) [title="Remove"]').map((element) => click(element) ) ); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); // Remove all the filters await Promise.all( - findAll('form fieldset:nth-of-type(3) [title="Remove"]').map((element) => + findAll('form fieldset:nth-of-type(2) [title="Remove"]').map((element) => click(element) ) ); await fillIn('[name="filters"]', 'filter'); - await click('form fieldset:nth-of-type(3) [title="Add"]'); + await click('form fieldset:nth-of-type(2) [title="Add"]'); await fillIn('[name="sync_interval_seconds"]', 10); await click('.rose-form-actions [type="submit"]'); @@ -198,12 +198,12 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { await fillIn('[name="name"]', name); // Remove all the preferred endpoints await Promise.all( - findAll('form fieldset:nth-of-type(2) [title="Remove"]').map((element) => + findAll('form fieldset:nth-of-type(1) [title="Remove"]').map((element) => click(element) ) ); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); await fillIn('[name="filter"]', 'filter'); await fillIn('[name="sync_interval_seconds"]', 10); await click('.rose-form-actions [type="submit"]'); diff --git a/ui/admin/tests/acceptance/onboarding/choose-path-test.js b/ui/admin/tests/acceptance/onboarding/choose-path-test.js index 7c4226850..836892631 100644 --- a/ui/admin/tests/acceptance/onboarding/choose-path-test.js +++ b/ui/admin/tests/acceptance/onboarding/choose-path-test.js @@ -21,15 +21,15 @@ module('Acceptance | onboarding | choose-path', function (hooks) { test('defaults to the guided path', async function (assert) { assert.expect(2); await visit(urls.choosePath); - assert.strictEqual(find('input[name=paths]:checked').value, 'guided'); + assert.strictEqual(find('input[name=path]:checked').value, 'guided'); assert.false(find('input[value=manual]').checked); }); test('updates to the manual path when manual card clicked', async function (assert) { assert.expect(2); await visit(urls.choosePath); - await click('[name="paths"][value="manual"]'); - assert.strictEqual(find('input[name=paths]:checked').value, 'manual'); + await click('[name="path"][value="manual"]'); + assert.strictEqual(find('input[name=path]:checked').value, 'manual'); assert.false(find('input[value=guided]').checked); }); @@ -43,7 +43,7 @@ module('Acceptance | onboarding | choose-path', function (hooks) { test('guided path directs user to /global/new route', async function (assert) { assert.expect(1); await visit(urls.choosePath); - await click('[name="paths"][value="manual"]'); + await click('[name="path"][value="manual"]'); await click('[type="submit"]'); assert.strictEqual(currentURL(), urls.newOrg); }); diff --git a/ui/admin/tests/acceptance/roles/create-test.js b/ui/admin/tests/acceptance/roles/create-test.js index 80cdce6fc..5268b3918 100644 --- a/ui/admin/tests/acceptance/roles/create-test.js +++ b/ui/admin/tests/acceptance/roles/create-test.js @@ -126,6 +126,6 @@ module('Acceptance | roles | create', function (hooks) { await click('form [type="submit"]'); await a11yAudit(); assert.ok(find('[role="alert"]')); - assert.ok(find('.rose-form-error-message')); + assert.ok(find('.hds-form-error__message')); }); }); diff --git a/ui/admin/tests/acceptance/roles/principals-test.js b/ui/admin/tests/acceptance/roles/principals-test.js index 5a6ad8b00..9d6c88b86 100644 --- a/ui/admin/tests/acceptance/roles/principals-test.js +++ b/ui/admin/tests/acceptance/roles/principals-test.js @@ -46,7 +46,8 @@ module('Acceptance | roles | principals', function (hooks) { ); principalsCount = this.server.db.roles[0].userIds.length + - this.server.db.roles[0].groupIds.length; + this.server.db.roles[0].groupIds.length + + this.server.db.roles[0].managedGroupIds.length; urls.roles = `/scopes/${instances.scopes.org.id}/roles`; urls.role = `${urls.roles}/${instances.role.id}`; urls.rolePrincipals = `${urls.role}/principals`; @@ -111,7 +112,7 @@ module('Acceptance | roles | principals', function (hooks) { test('select and save principals to add', async function (assert) { assert.expect(3); - instances.role.update({ userIds: [], groupIds: [] }); + instances.role.update({ userIds: [], groupIds: [], managedGroupIds: [] }); await visit(urls.rolePrincipals); assert.strictEqual(findAll('tbody tr').length, 0); await click('.rose-layout-page-actions a'); @@ -154,7 +155,7 @@ module('Acceptance | roles | principals', function (hooks) { } ); }); - instances.role.update({ userIds: [], groupIds: [] }); + instances.role.update({ userIds: [], groupIds: [], managedGroupIds: [] }); await visit(urls.addPrincipals); await click('tbody label'); await click('form [type="submit"]'); diff --git a/ui/admin/tests/acceptance/roles/update-test.js b/ui/admin/tests/acceptance/roles/update-test.js index 95486e164..71b17ae5d 100644 --- a/ui/admin/tests/acceptance/roles/update-test.js +++ b/ui/admin/tests/acceptance/roles/update-test.js @@ -113,7 +113,7 @@ module('Acceptance | roles | update', function (hooks) { 'Displays primary error message.' ); assert.strictEqual( - find('.rose-form-error-message').textContent.trim(), + find('.hds-form-error__message').textContent.trim(), 'Name is required.', 'Displays field-level errors.' ); diff --git a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js index 84f4048a4..8bd4eb77d 100644 --- a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js @@ -15,6 +15,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { let credentialSourceCount; let randomlySelectedCredentialLibraries; let randomlySelectedCredentials; + let featuresService; const instances = { scopes: { @@ -37,11 +38,14 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { target: null, credentialLibraries: null, credentialLibrary: null, + credential: null, + jsonCredential: null, addBrokeredCredentialSources: null, brokeredCredentialSources: null, }; hooks.beforeEach(function () { + featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -60,7 +64,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { type: 'static', scope: instances.scopes.project, }); - instances.credentials = this.server.createList('credential', 2, { + instances.credentials = this.server.createList('credential', 3, { scope: instances.scopes.project, credentialStore: instances.staticCredentialStore, }); @@ -100,6 +104,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { urls.brokeredCredentialSources = `${urls.target}/brokered-credential-sources`; urls.credentialLibrary = `${urls.projectScope}/credential-stores/${instances.credentialLibrary.credentialStoreId}/credential-libraries/${instances.credentialLibrary.id}`; urls.credential = `${urls.projectScope}/credential-stores/${instances.credential.credentialStoreId}/credentials/${instances.credential.id}`; + urls.jsonCredential = `${urls.projectScope}/credential-stores/${instances.credentials[2].credentialStoreId}/credentials/${instances.credentials[2].id}`; urls.addBrokeredCredentialSources = `${urls.target}/add-brokered-credential-sources`; getCredentialLibraryCount = () => this.server.schema.credentialLibraries.all().models.length; @@ -137,6 +142,21 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { assert.strictEqual(currentURL(), urls.credential); }); + test('cannot navigate to a json type credential when feature is disabled', async function (assert) { + featuresService.disable('json-credentials'); + assert.expect(2); + const jsonCredential = instances.credentials[2]; + instances.target.update({ + brokeredCredentialSourceIds: [...randomlySelectedCredentials], + }); + await visit(urls.brokeredCredentialSources); + + assert + .dom('.rose-table-row:nth-child(3)') + .includesText(jsonCredential.name); + assert.dom(`[href="${urls.jsonCredential}"]`).doesNotExist(); + }); + test('visiting add brokered credential sources', async function (assert) { assert.expect(1); await visit(urls.addBrokeredCredentialSources); @@ -157,7 +177,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { test('displays list of brokered credential sources with only credential libraries available', async function (assert) { assert.expect(2); instances.target.update({ - brokeredCredentialSourceIds: [...randomlySelectedCredentialLibraries], + brokeredCredentialSourceIds: [...randomlySelectedCredentials], }); await visit(urls.addBrokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, getCredentialLibraryCount()); @@ -300,12 +320,16 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { brokeredCredentialSourceIds: [...randomlySelectedCredentialLibraries], }); const credentialLibraryCount = getCredentialLibraryCount(); + const availableCredentialsCount = getCredentialCount(); await visit(urls.brokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount); await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount + 1); + assert.strictEqual( + findAll('tbody tr').length, + availableCredentialsCount + 1 + ); }); test('can remove a username & password type credential', async function (assert) { @@ -314,12 +338,16 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { brokeredCredentialSourceIds: [...randomlySelectedCredentials], }); const credentialCount = getCredentialCount(); + const availableCredentialsCount = getCredentialLibraryCount(); await visit(urls.brokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialCount); await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, credentialCount + 1); + assert.strictEqual( + findAll('tbody tr').length, + availableCredentialsCount + 1 + ); }); test('cannot remove credential libraries without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/create-test.js b/ui/admin/tests/acceptance/targets/create-test.js index 856d33fd2..cdf741c34 100644 --- a/ui/admin/tests/acceptance/targets/create-test.js +++ b/ui/admin/tests/acceptance/targets/create-test.js @@ -16,6 +16,7 @@ import { //currentSession, //invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Acceptance | targets | create', function (hooks) { setupApplicationTest(hooks); @@ -71,31 +72,32 @@ module('Acceptance | targets | create', function (hooks) { // Generate resource counter getTargetCount = () => this.server.schema.targets.all().models.length; getSSHTargetCount = () => - this.server.schema.targets.where({ type: 'ssh' }).models.length; + this.server.schema.targets.where({ type: TYPE_TARGET_SSH }).models.length; getTCPTargetCount = () => - this.server.schema.targets.where({ type: 'tcp' }).models.length; + this.server.schema.targets.where({ type: TYPE_TARGET_TCP }).models.length; authenticateSession({}); }); - test('defaults to a new TCP target when no query param provided', async function (assert) { + test('defaults to type `ssh` when no query param provided', async function (assert) { assert.expect(1); await visit(urls.newTarget); - assert.strictEqual(find('input[name=tcp]:checked').value, 'tcp'); + assert.strictEqual(find('[name="type"]:checked').value, TYPE_TARGET_SSH); }); - test('can create new targets of type TCP', async function (assert) { + test('can create type `ssh`', async function (assert) { assert.expect(4); const targetCount = getTargetCount(); - const tcpTargetCount = getTCPTargetCount(); + const sshTargetCount = getSSHTargetCount(); await visit(urls.targets); await click(`[href="${urls.newTarget}"]`); + await click('[value="ssh"]'); await fillIn('[name="name"]', 'random string'); await fillIn('[name="worker_filter"]', 'random filter'); await click('[type="submit"]'); + assert.strictEqual(getSSHTargetCount(), sshTargetCount + 1); assert.strictEqual(getTargetCount(), targetCount + 1); - assert.strictEqual(getTCPTargetCount(), tcpTargetCount + 1); assert.strictEqual( this.server.schema.targets.all().models[getTargetCount() - 1].name, 'random string' @@ -107,20 +109,20 @@ module('Acceptance | targets | create', function (hooks) { ); }); - test('can create new targets of type SSH', async function (assert) { + test('can create type `tcp`', async function (assert) { assert.expect(4); const targetCount = getTargetCount(); - const sshTargetCount = getSSHTargetCount(); + const tcpTargetCount = getTCPTargetCount(); await visit(urls.targets); await click(`[href="${urls.newTarget}"]`); + await click('[value="tcp"]'); await fillIn('[name="name"]', 'random string'); - await click('[value="ssh"]'); await fillIn('[name="worker_filter"]', 'random filter'); await click('[type="submit"]'); - assert.strictEqual(getSSHTargetCount(), sshTargetCount + 1); assert.strictEqual(getTargetCount(), targetCount + 1); + assert.strictEqual(getTCPTargetCount(), tcpTargetCount + 1); assert.strictEqual( this.server.schema.targets.all().models[getTargetCount() - 1].name, 'random string' diff --git a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js index fed2a6956..feccc7ee5 100644 --- a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js @@ -5,6 +5,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_SSH } from 'api/models/target'; module( 'Acceptance | targets | injected application credential sources', @@ -78,7 +79,7 @@ module( instances.credential = instances.credentials[0]; instances.target = this.server.create('target', { scope: instances.scopes.project, - type: 'ssh', + type: TYPE_TARGET_SSH, }); randomlySelectedCredentials = this.server.schema.credentials .all() diff --git a/ui/admin/tests/acceptance/targets/read-test.js b/ui/admin/tests/acceptance/targets/read-test.js index 5eaaba87a..e49df488f 100644 --- a/ui/admin/tests/acceptance/targets/read-test.js +++ b/ui/admin/tests/acceptance/targets/read-test.js @@ -9,6 +9,7 @@ import { //currentSession, //invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Acceptance | targets | read', function (hooks) { setupApplicationTest(hooks); @@ -44,11 +45,11 @@ module('Acceptance | targets | read', function (hooks) { scope: { id: instances.scopes.org.id, type: 'org' }, }); instances.sshTarget = this.server.create('target', { - type: 'ssh', + type: TYPE_TARGET_SSH, scope: instances.scopes.project, }); instances.tcpTarget = this.server.create('target', { - type: 'tcp', + type: TYPE_TARGET_TCP, scope: instances.scopes.project, }); // Generate route URLs for resources diff --git a/ui/admin/tests/acceptance/workers/create-test.js b/ui/admin/tests/acceptance/workers/create-test.js index e49fac281..2f4c9ae6f 100644 --- a/ui/admin/tests/acceptance/workers/create-test.js +++ b/ui/admin/tests/acceptance/workers/create-test.js @@ -60,6 +60,26 @@ module('Acceptance | workers | create', function (hooks) { assert.dom(labels[2]).hasText('Initial Upstreams'); }); + test('download and install step shows correct oss instructions', async function (assert) { + assert.expect(2); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('byow-pki-hcp-cluster-id'); + await visit(newWorkerURL); + const createSection = findAll('.worker-create-section'); + assert.dom(createSection[1]).includesText('curl -fsSL'); + assert.dom(createSection[1]).doesNotIncludeText('wget -q'); + }); + + test('download and install step shows correct hcp instructions', async function (assert) { + assert.expect(2); + const featuresService = this.owner.lookup('service:features'); + featuresService.enable('byow-pki-hcp-cluster-id'); + await visit(newWorkerURL); + const createSection = findAll('.worker-create-section'); + assert.dom(createSection[1]).includesText('wget -q'); + assert.dom(createSection[1]).doesNotIncludeText('curl -fsSL'); + }); + test('Users can navigate to new workers route with proper authorization', async function (assert) { assert.expect(2); await visit(newWorkerURL); diff --git a/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js b/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js index 8b2db71f5..7853fbf62 100644 --- a/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js +++ b/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js @@ -3,18 +3,21 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -module('Integration | Component | form/field/json-secret/skeleton', function (hooks) { - setupRenderingTest(hooks); +module( + 'Integration | Component | form/field/json-secret/skeleton', + function (hooks) { + setupRenderingTest(hooks); - test('it renders', async function (assert) { - assert.expect(2); - await render(hbs` + test('it renders', async function (assert) { + assert.expect(2); + await render(hbs` Skeleton Message `); - assert.dom('.secret-editor-skeleton-wrapper').isVisible(); - assert.dom('.secret-editor-skeleton-message').hasText('Skeleton Message'); - }); -}); \ No newline at end of file + assert.dom('.secret-editor-skeleton-wrapper').isVisible(); + assert.dom('.secret-editor-skeleton-message').hasText('Skeleton Message'); + }); + } +); diff --git a/ui/admin/tests/integration/components/form/field/json-secret-test.js b/ui/admin/tests/integration/components/form/field/json-secret-test.js index d7f80cdb9..2b1112df0 100644 --- a/ui/admin/tests/integration/components/form/field/json-secret-test.js +++ b/ui/admin/tests/integration/components/form/field/json-secret-test.js @@ -26,7 +26,9 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').isVisible(); - assert.dom('.secret-editor-json .CodeMirror-line').hasText(this.model.json_object); + assert + .dom('.secret-editor-json .CodeMirror-line') + .hasText(this.model.json_object); }); test('it renders the disabled editor', async function (assert) { @@ -42,7 +44,9 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').doesNotExist(); - assert.dom('.secret-editor-skeleton-wrapper').hasText('This secret is saved but won\'t be displayed'); + assert + .dom('.secret-editor-skeleton-wrapper') + .hasText("This secret is saved but won't be displayed"); }); test('it renders the actionable editor', async function (assert) { @@ -58,7 +62,9 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').doesNotExist(); - assert.dom('.secret-editor-skeleton-wrapper').hasText('Click to replace this secret'); + assert + .dom('.secret-editor-skeleton-wrapper') + .hasText('Click to replace this secret'); }); test('it renders the active editor from the actionable view', async function (assert) { @@ -74,9 +80,11 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').doesNotExist(); - assert.dom('.secret-editor-skeleton-wrapper').hasText('Click to replace this secret'); + assert + .dom('.secret-editor-skeleton-wrapper') + .hasText('Click to replace this secret'); - await click('button') + await click('button'); assert.dom('.secret-editor-json').isVisible(); }); -}); \ No newline at end of file +}); diff --git a/ui/admin/tests/integration/components/info-field-test.js b/ui/admin/tests/integration/components/info-field-test.js index 691c9d04a..ba3a61b84 100644 --- a/ui/admin/tests/integration/components/info-field-test.js +++ b/ui/admin/tests/integration/components/info-field-test.js @@ -11,15 +11,15 @@ module('Integration | Component | info-field', function (hooks) { await render( hbs` - + Type - JSON + Description ` ); assert.dom('.info-field').isVisible(); assert.dom('.info-field .hds-form-label').hasText('Type'); - assert.dom('.info-field .hds-form-helper-text').hasText('JSON'); + assert.dom('.info-field .hds-form-helper-text').hasText('Description'); assert.dom('.info-field .hds-form-text-input').isVisible(); }); @@ -28,9 +28,9 @@ module('Integration | Component | info-field', function (hooks) { await render( hbs` - + Type - JSON + Description ` ); @@ -38,7 +38,7 @@ module('Integration | Component | info-field', function (hooks) { assert.dom('.info-field .hds-form-label').hasText('Type'); assert .dom('.info-field .hds-form-helper-text:last-of-type') - .hasText('JSON'); + .hasText('Description'); assert.dom('[data-test-icon="apple"]').isVisible(); assert.dom('.info-field .hds-form-text-input').isVisible(); }); diff --git a/ui/admin/tests/unit/abilities/credential-test.js b/ui/admin/tests/unit/abilities/credential-test.js new file mode 100644 index 000000000..a8a719f7e --- /dev/null +++ b/ui/admin/tests/unit/abilities/credential-test.js @@ -0,0 +1,77 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | credential', function (hooks) { + setupTest(hooks); + + let features; + + hooks.beforeEach(function () { + // Enable json-credentials feature by default + features = this.owner.lookup('service:features'); + features.enable('json-credentials'); + }); + + test('can read credentials, including JSON credentials, when authorized and json-credentials feature is enabled', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { + authorized_actions: ['read'], + type: 'json', + }); + assert.true(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.true(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.true(service.can('read credential', credential)); + }); + + test('cannot read credentials, including JSON credentials, when unauthorized and json-credentials feature is enabled', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { + authorized_actions: [], + type: 'json', + }); + assert.false(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.false(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.false(service.can('read credential', credential)); + }); + + test('cannot read credentials, including JSON credentials, when unauthorized and json-credentials feature is disabled', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('json-credentials'); + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { + authorized_actions: [], + type: 'json', + }); + assert.false(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.false(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.false(service.can('read credential', credential)); + }); + + test('can read credentials, excepting JSON credentials, when authorized and json-credentials feature is disabled', function (assert) { + assert.expect(3); + features.disable('json-credentials'); + const service = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { + authorized_actions: ['read'], + type: 'json', + }); + assert.false(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.true(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.true(service.can('read credential', credential)); + }); +}); diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs index 56ee4d216..33d9fe558 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs @@ -30,7 +30,7 @@ {{#if host.isStatic}} {{host.address}} {{else}} - + {{/if}} diff --git a/ui/desktop/electron-app/yarn.lock b/ui/desktop/electron-app/yarn.lock index eb0ed532d..570b7b72c 100644 --- a/ui/desktop/electron-app/yarn.lock +++ b/ui/desktop/electron-app/yarn.lock @@ -3262,9 +3262,9 @@ punycode@^2.1.0, punycode@^2.1.1: integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== queue-microtask@^1.2.2: version "1.2.3" diff --git a/ui/desktop/tests/acceptance/projects/targets-test.js b/ui/desktop/tests/acceptance/projects/targets-test.js index cb4d2c7ba..d08ff81bf 100644 --- a/ui/desktop/tests/acceptance/projects/targets-test.js +++ b/ui/desktop/tests/acceptance/projects/targets-test.js @@ -10,6 +10,7 @@ import { authenticateSession, invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_SSH } from 'api/models/target'; module('Acceptance | projects | targets', function (hooks) { setupApplicationTest(hooks); @@ -247,7 +248,7 @@ module('Acceptance | projects | targets', function (hooks) { test('displays the correct target type (SSH) in the success dialog body', async function (assert) { assert.expect(1); instances.target.update({ - type: 'ssh', + type: TYPE_TARGET_SSH, }); stubs.ipcService.withArgs('cliExists').returns(true); stubs.ipcService.withArgs('connect').returns({ diff --git a/yarn.lock b/yarn.lock index 23144f89f..31dced969 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3907,6 +3907,15 @@ ember-cli-version-checker "^5.1.2" semver "^7.3.5" +"@embroider/addon-shim@^1.0.0": + version "1.8.4" + resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.4.tgz#0e7f32c5506bf0f3eb0840506e31c36c7053763c" + integrity sha512-sFhfWC0vI18KxVenmswQ/ShIvBg4juL8ubI+Q3NTSdkCTeaPQ/DIOUF6oR5DCQ8eO/TkIaw+kdG3FkTY6yNJqA== + dependencies: + "@embroider/shared-internals" "^2.0.0" + broccoli-funnel "^3.0.8" + semver "^7.3.8" + "@embroider/addon-shim@^1.3.0", "@embroider/addon-shim@^1.5.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.3.tgz#2368510b8ce42d50d02cb3289c32e260dfa34bd9" @@ -3995,18 +4004,6 @@ resolve "^1.8.1" semver "^7.3.2" -"@embroider/macros@^0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-0.41.0.tgz#3e78b6f388d7229906abf4c75edfff8bb0208aca" - integrity sha512-QISzwEEfLsskZeL0jyZDs1RoQSotwBWj+4upTogNHuxQP5j/9H3IMG/3QB1gh8GEpbudATb/cS4NDYK3UBxufw== - dependencies: - "@embroider/shared-internals" "0.41.0" - assert-never "^1.1.0" - ember-cli-babel "^7.23.0" - lodash "^4.17.10" - resolve "^1.8.1" - semver "^7.3.2" - "@embroider/shared-internals@0.40.0": version "0.40.0" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-0.40.0.tgz#2f768c60f4f35ba5f9228f046f70324851e8bfe2" @@ -4020,19 +4017,6 @@ semver "^7.3.2" typescript-memoize "^1.0.0-alpha.3" -"@embroider/shared-internals@0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-0.41.0.tgz#2553f026d4f48ea1fd11235501feb63bf49fa306" - integrity sha512-fiqUVB6cfh2UBEFE4yhT5EzagkZ1Q26+OhBV0nJszFEJZx4DqVIb3pxSSZ8P+HhpxuJsQ2XpMA/j02ZPFZfbdQ== - dependencies: - ember-rfc176-data "^0.3.17" - fs-extra "^7.0.1" - lodash "^4.17.10" - pkg-up "^3.1.0" - resolve-package-path "^1.2.2" - semver "^7.3.2" - typescript-memoize "^1.0.0-alpha.3" - "@embroider/shared-internals@1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.0.tgz#4c9b563e5f155410d982a30667b027a227be7312" @@ -4061,6 +4045,20 @@ semver "^7.3.5" typescript-memoize "^1.0.1" +"@embroider/shared-internals@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.0.0.tgz#d8205ec6944362aeecfbb51143db352430ced316" + integrity sha512-qZ2/xky9mWm5YC6noOa6AiAwgISEQ78YTZNv4SNu2PFgEK/H+Ha/3ddngzGSsnXkVnIHZyxIBzhxETonQYHY9g== + dependencies: + babel-import-util "^1.1.0" + ember-rfc176-data "^0.3.17" + fs-extra "^9.1.0" + js-string-escape "^1.0.1" + lodash "^4.17.21" + resolve-package-path "^4.0.1" + semver "^7.3.5" + typescript-memoize "^1.0.1" + "@embroider/test-setup@^0.48.1": version "0.48.1" resolved "https://registry.yarnpkg.com/@embroider/test-setup/-/test-setup-0.48.1.tgz#f64da84f8241433d0ba92154fa1eb3137ecd4df8" @@ -7393,9 +7391,9 @@ "@xtuc/long" "4.2.2" "@xmldom/xmldom@^0.8.0": - version "0.8.2" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.2.tgz#b695ff674e8216efa632a3d36ad51ae9843380c0" - integrity sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ== + version "0.8.6" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440" + integrity sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -7440,15 +7438,7 @@ abortcontroller-polyfill@^1.7.3: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -accepts@~1.3.8: +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -7476,13 +7466,6 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= - dependencies: - acorn "^3.0.4" - acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -7493,12 +7476,7 @@ acorn-walk@^7.1.1, acorn-walk@^7.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^3.0.4: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= - -acorn@^5.0.0, acorn@^5.5.0, acorn@^5.5.3: +acorn@^5.0.0, acorn@^5.5.3: version "5.7.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== @@ -7623,11 +7601,6 @@ ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" - integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw= - ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -7640,14 +7613,6 @@ ajv-keywords@^5.0.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^4.7.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -7722,11 +7687,6 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" - integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= - ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -7910,7 +7870,7 @@ array-find-index@^1.0.1, array-find-index@^1.0.2: array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== array-includes@^3.0.3: version "3.1.3" @@ -8668,7 +8628,7 @@ babel-types@^6.26.0: babel6-plugin-strip-class-callcheck@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/babel6-plugin-strip-class-callcheck/-/babel6-plugin-strip-class-callcheck-6.0.0.tgz#de841c1abebbd39f78de0affb2c9a52ee228fddf" - integrity sha1-3oQcGr6705943gr/ssmlLuIo/d8= + integrity sha512-biNFJ7JAK4+9BwswDGL0dmYpvXHvswOFR/iKg3Q/f+pNxPEa5bWZkLHI1fW4spPytkHGMe7f/XtYyhzml9hiWg== babylon@^6.18.0: version "6.18.0" @@ -8823,26 +8783,10 @@ bn.js@^5.0.0, bn.js@^5.1.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== -body-parser@1.19.0, body-parser@^1.17.0, body-parser@^1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== +body-parser@1.20.1, body-parser@^1.17.0, body-parser@^1.19.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: bytes "3.1.2" content-type "~1.0.4" @@ -8852,7 +8796,7 @@ body-parser@1.20.0: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.10.3" + qs "6.11.0" raw-body "2.5.1" type-is "~1.6.18" unpipe "1.0.0" @@ -9728,11 +9672,6 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -9869,18 +9808,6 @@ call-me-maybe@^1.0.1: resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= - dependencies: - callsites "^0.2.0" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= - callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -10193,11 +10120,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -10272,13 +10194,6 @@ cli-columns@^3.1.2: string-width "^2.0.0" strip-ansi "^3.0.1" -cli-cursor@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= - dependencies: - restore-cursor "^1.0.1" - cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -10564,7 +10479,7 @@ commander@7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^2.20.0, commander@^2.6.0, commander@^2.8.1, commander@^2.9.0: +commander@^2.20.0, commander@^2.6.0, commander@^2.9.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -10664,7 +10579,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.2: +concat-stream@^1.5.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -10749,13 +10664,6 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -10788,12 +10696,7 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1, cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== cookie@0.5.0: version "0.5.0" @@ -11376,9 +11279,9 @@ decimal.js@^10.2.1: integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== decompress-response@^6.0.0: version "6.0.0" @@ -11496,11 +11399,6 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - detab@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" @@ -11647,14 +11545,6 @@ doctoc@^2.2.0: underscore "^1.13.2" update-section "^0.3.3" -doctrine@^1.2.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -11876,7 +11766,7 @@ editor@~1.0.0: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-notarize@^1.1.1: version "1.2.1" @@ -12446,7 +12336,7 @@ ember-cli-terser@^4.0.2: ember-cli-test-info@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-cli-test-info/-/ember-cli-test-info-1.0.0.tgz#ed4e960f249e97523cf891e4aed2072ce84577b4" - integrity sha1-7U6WDySel1I8+JHkrtIHLOhFd7Q= + integrity sha512-dEVTIpmUfCzweC97NGf6p7L6XKBwV2GmSM4elmzKvkttEp5P7AvGA9uGyN4GqFq+RwhW+2b0I2qlX00w+skm+A== dependencies: ember-cli-string-utils "^1.0.0" @@ -12878,16 +12768,13 @@ ember-fetch@^8.1.1: node-fetch "^2.6.1" whatwg-fetch "^3.6.2" -ember-focus-trap@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/ember-focus-trap/-/ember-focus-trap-0.7.0.tgz#8c3fa66a0d393dad2994db82e2fa227103875fc0" - integrity sha512-WHOD8jTcCzsRb0cU8J5SKObrxbdD8rPSWrSUjZ2QYu9dVbaXg6/hZxcN5JrmPY1ArnsRaLMPdOUALYeZTP29og== +ember-focus-trap@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ember-focus-trap/-/ember-focus-trap-1.0.1.tgz#a99565f6ce55d500b92a0965e79e3ad04219f157" + integrity sha512-ZUyq5ZkIuXp+ng9rCMkqBh36/V95PltL7iljStkma4+651xlAy3Z84L9WOu/uOJyVpNUxii8RJBbAySHV6c+RQ== dependencies: - "@embroider/macros" "^0.41.0" - ember-auto-import "^1.11.2" - ember-cli-babel "^7.26.3" - ember-modifier-manager-polyfill "^1.2.0" - focus-trap "^6.4.0" + "@embroider/addon-shim" "^1.0.0" + focus-trap "^6.7.1" "ember-get-config@^0.2.4 || ^0.3.0": version "0.3.0" @@ -13300,7 +13187,7 @@ emotion-theming@^10.0.27: encodeurl@^1.0.2, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== encoding@^0.1.11, encoding@^0.1.12: version "0.1.13" @@ -13636,7 +13523,7 @@ escape-goat@^2.0.0: escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: version "2.0.0" @@ -13775,45 +13662,6 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^2.7.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11" - integrity sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE= - dependencies: - chalk "^1.1.3" - concat-stream "^1.4.6" - debug "^2.1.1" - doctrine "^1.2.2" - es6-map "^0.1.3" - escope "^3.6.0" - espree "^3.1.6" - estraverse "^4.2.0" - esutils "^2.0.2" - file-entry-cache "^1.1.1" - glob "^7.0.3" - globals "^9.2.0" - ignore "^3.1.2" - imurmurhash "^0.1.4" - inquirer "^0.12.0" - is-my-json-valid "^2.10.0" - is-resolvable "^1.0.0" - js-yaml "^3.5.1" - json-stable-stringify "^1.0.0" - levn "^0.3.0" - lodash "^4.0.0" - mkdirp "^0.5.0" - optionator "^0.8.1" - path-is-absolute "^1.0.0" - path-is-inside "^1.0.1" - pluralize "^1.2.1" - progress "^1.1.8" - require-uncached "^1.0.2" - shelljs "^0.6.0" - strip-json-comments "~1.0.1" - table "^3.7.8" - text-table "~0.2.0" - user-home "^2.0.0" - eslint@^7.27.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -13906,14 +13754,6 @@ esm@^3.2.25, esm@^3.2.4: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^3.1.6: - version "3.5.4" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" - integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== - dependencies: - acorn "^5.5.0" - acorn-jsx "^3.0.0" - espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -13961,7 +13801,7 @@ estraverse-fb@^1.3.2: resolved "https://registry.yarnpkg.com/estraverse-fb/-/estraverse-fb-1.3.2.tgz#d323a4cb5e5ac331cea033413a9253e1643e07c4" integrity sha1-0yOky15awzHOoDNBOpJT4WQ+B8Q= -estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -13984,7 +13824,7 @@ esutils@^2.0.2: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== event-emitter@~0.3.5: version "0.3.5" @@ -14131,11 +13971,6 @@ execall@^2.0.0: dependencies: clone-regexp "^2.1.0" -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" - integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -14183,50 +14018,14 @@ expect@^27.4.6: jest-matcher-utils "^27.4.6" jest-message-util "^27.4.6" -express@^4.10.7, express@^4.13.1, express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^4.17.2: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== +express@^4.10.7, express@^4.13.1, express@^4.17.1, express@^4.17.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.0" + body-parser "1.20.1" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.5.0" @@ -14245,7 +14044,7 @@ express@^4.17.2: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.10.3" + qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.18.0" @@ -14507,14 +14306,6 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -figures@^1.3.5: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -14529,14 +14320,6 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^1.1.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8" - integrity sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g= - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -14617,7 +14400,7 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== -finalhandler@1.1.2, finalhandler@~1.1.2: +finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== @@ -14814,16 +14597,6 @@ fixturify@^2.1.0: matcher-collection "^2.0.1" walk-sync "^2.0.2" -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== - dependencies: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -14863,12 +14636,12 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -focus-trap@^6.4.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.6.1.tgz#761ce2c82ddd72beeb049e968bc8414e25b704aa" - integrity sha512-x9BWuAeF5UrfWuYKJ3jYrjcVYSYptS9CqtxH5IH7lPlZrMsaugKeAa0HtoZSBZe5DmeTMx2m0qY464ZMzqarzw== +focus-trap@^6.7.1: + version "6.9.4" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.9.4.tgz#436da1a1d935c48b97da63cd8f361c6f3aa16444" + integrity sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw== dependencies: - tabbable "^5.2.1" + tabbable "^5.3.3" follow-redirects@^1.0.0, follow-redirects@^1.14.8: version "1.15.2" @@ -14960,7 +14733,7 @@ fragment-cache@^0.2.1: fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== from2@^1.3.0: version "1.3.0" @@ -14978,13 +14751,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -front-matter@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb" - integrity sha1-91mDufL0E75ljJPf172M5AePXNs= - dependencies: - js-yaml "^3.4.6" - fs-extra@8.1.0, fs-extra@^8.0.0, fs-extra@^8.0.1, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -15024,15 +14790,6 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" - integrity sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^3.0.0" - universalify "^0.1.0" - fs-extra@^4.0.0, fs-extra@^4.0.2, fs-extra@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -15311,7 +15068,16 @@ get-installed-path@^2.0.3: dependencies: global-modules "1.0.0" -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== @@ -15488,7 +15254,7 @@ glob@^5.0.10: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.4, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== @@ -15612,7 +15378,7 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" -globals@^9.18.0, globals@^9.2.0: +globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== @@ -15714,22 +15480,6 @@ globrex@^0.1.2: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== -globule@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" - integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - -gonzales-pe-sl@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz#6a868bc380645f141feeb042c6f97fcc71b59fe6" - integrity sha1-aoaLw4BkXxQf7rBCxvl/zHG1n+Y= - dependencies: - minimist "1.1.x" - gonzales-pe@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" @@ -16225,17 +15975,6 @@ http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -16257,17 +15996,6 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-parser-js@>=0.5.1: version "0.5.3" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" @@ -16425,11 +16153,6 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^3.1.2: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - ignore@^4.0.3, ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -16605,25 +16328,6 @@ inquirer@6.5.2, inquirer@^6: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" - integrity sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34= - dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - figures "^1.3.5" - lodash "^4.3.0" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -17018,7 +16722,7 @@ is-my-ip-valid@^1.0.0: resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== -is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: +is-my-json-valid@^2.12.4: version "2.20.5" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz#5eca6a8232a687f68869b7361be1612e7512e5df" integrity sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A== @@ -17152,11 +16856,6 @@ is-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA== -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - is-root@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" @@ -17970,7 +17669,7 @@ js-yaml@4.0.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.2.5, js-yaml@^3.2.7, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4: +js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.2.5, js-yaml@^3.2.7: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -18151,13 +17850,6 @@ json5@^0.5.1: resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - json5@^2.1.2, json5@^2.1.3, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" @@ -18170,13 +17862,6 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" -jsonfile@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" - integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY= - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -18294,11 +17979,6 @@ known-css-properties@^0.25.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776" integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA== -known-css-properties@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4" - integrity sha512-QMQcnKAiQccfQTqtBh/qwquGZ2XK/DXND1jrcN9M8gMMy99Gwla7GQjndVUsEqIaRyP6bsFRuhwRj5poafBGJQ== - language-subtag-registry@~0.3.2: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -18355,14 +18035,6 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -18371,6 +18043,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + libcipm@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/libcipm/-/libcipm-4.0.8.tgz#dcea4919e10dfbce420327e63901613b9141bc89" @@ -18630,24 +18310,15 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@2.0.0, loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== +loader-utils@2.0.0, loader-utils@^1.2.3, loader-utils@^1.4.0, loader-utils@^2.0.0, loader-utils@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" json5 "^2.1.2" -loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" - integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - loader.js@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/loader.js/-/loader.js-4.7.0.tgz#a1a52902001c83631efde9688b8ab3799325ef1f" @@ -18813,11 +18484,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.capitalize@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" - integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= - lodash.castarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" @@ -18933,7 +18599,7 @@ lodash.isplainobject@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= -lodash.kebabcase@^4.0.0, lodash.kebabcase@^4.1.1: +lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= @@ -19042,7 +18708,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha512-M3MefBwfDhgKgINVuBJCO1YR3+gf6s9HNJsIiZ/Ru77Ws6uTb9eBuvrkpzO+9iLoAaRodGuq7tyrPCx+74QYGQ== -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.7.0, lodash@~4.17.10: +lodash@^4.17.10, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -19530,7 +19196,7 @@ meant@^1.0.2: media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== mem@^4.0.0, mem@^4.3.0: version "4.3.0" @@ -19620,7 +19286,7 @@ meow@^9.0.0: merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-stream@^2.0.0: version "2.0.0" @@ -19640,7 +19306,7 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -merge@^1.2.0, merge@^2.1.0, merge@^2.1.1: +merge@^2.1.0, merge@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== @@ -19648,7 +19314,7 @@ merge@^1.2.0, merge@^2.1.0, merge@^2.1.1: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== microevent.ts@~0.1.1: version "0.1.1" @@ -19780,7 +19446,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.11, mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.7: +mime-types@^2.1.11, mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.7: version "2.1.32" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== @@ -19794,7 +19460,7 @@ mime-types@^2.1.25: dependencies: mime-db "1.50.0" -mime-types@~2.1.34: +mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -19865,7 +19531,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -19895,7 +19561,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@1.1.x, minimist@1.2.5, minimist@^0.2.1, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: +minimist@1.2.5, minimist@^0.2.1, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -20124,7 +19790,7 @@ move-concurrently@^1.0.1: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.1: version "2.1.1" @@ -20146,11 +19812,6 @@ mustache@^4.2.0: resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== -mute-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" - integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -20198,11 +19859,6 @@ ncp@^2.0.0: resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - negotiator@0.6.3, negotiator@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -20506,7 +20162,7 @@ npm-conf@^1.1.3: npm-git-info@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/npm-git-info/-/npm-git-info-1.0.3.tgz#a933c42ec321e80d3646e0d6e844afe94630e1d5" - integrity sha1-qTPELsMh6A02RuDW6ESv6UYw4dU= + integrity sha512-i5WBdj4F/ULl16z9ZhsJDMl1EQCMQhHZzBwNnKL2LOA+T8IHNeRkLCVz9uVV9SzUdGTbDq+1oXhIYMe+8148vw== npm-install-checks@^3.0.2: version "3.0.2" @@ -20862,12 +20518,12 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== -object-inspect@^1.11.0, object-inspect@^1.9.0: +object-inspect@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.12.2: +object-inspect@^1.12.2, object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== @@ -20969,11 +20625,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0, once@~1.4.0: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -21536,7 +21187,7 @@ path-root@^0.1.1: path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-to-regexp@^1.7.0: version "1.8.0" @@ -21723,11 +21374,6 @@ plist@^3.0.0, plist@^3.0.1, plist@^3.0.4, plist@^3.0.5: base64-js "^1.5.1" xmlbuilder "^15.1.1" -pluralize@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" - integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU= - pngjs@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" @@ -22096,11 +21742,6 @@ progress-stream@^1.1.0: speedometer "~0.1.2" through2 "~0.2.3" -progress@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" - integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= - progress@^2.0.0, progress@^2.0.1, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -22233,7 +21874,7 @@ protoduck@^5.0.1: dependencies: genfun "^5.0.0" -proxy-addr@~2.0.5, proxy-addr@~2.0.7: +proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -22341,22 +21982,10 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -qs@^6.10.0, qs@^6.4.0: - version "6.10.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" - integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== +qs@6.11.0, qs@^6.10.0, qs@^6.4.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: side-channel "^1.0.4" @@ -22463,16 +22092,6 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" @@ -22873,15 +22492,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -readline2@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" - integrity sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - mute-stream "0.0.5" - recast@^0.18.1: version "0.18.10" resolved "https://registry.yarnpkg.com/recast/-/recast-0.18.10.tgz#605ebbe621511eb89b6356a7e224bff66ed91478" @@ -23326,14 +22936,6 @@ require-relative@^0.8.7: resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= -require-uncached@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - requireindex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" @@ -23374,11 +22976,6 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -23483,14 +23080,6 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" - integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -23624,13 +23213,6 @@ rsvp@~3.2.1: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a" integrity sha1-B8tKXfJa3Z6Cbrxn3Mn9idsn2Eo= -run-async@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" - integrity sha1-yK1KXhEGYeQCp9IbUw4AnyX444k= - dependencies: - once "^1.3.0" - run-async@^2.2.0, run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -23650,11 +23232,6 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rx-lite@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" - integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= - rxjs@^6.4.0, rxjs@^6.6.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -23736,26 +23313,6 @@ sane@^5.0.1: minimist "^1.1.1" walker "~1.0.5" -sass-lint@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.13.1.tgz#5fd2b2792e9215272335eb0f0dc607f61e8acc8f" - integrity sha512-DSyah8/MyjzW2BWYmQWekYEKir44BpLqrCFsgs9iaWiVTcwZfwXHF586hh3D1n+/9ihUNMfd8iHAyb9KkGgs7Q== - dependencies: - commander "^2.8.1" - eslint "^2.7.0" - front-matter "2.1.2" - fs-extra "^3.0.1" - glob "^7.0.0" - globule "^1.0.0" - gonzales-pe-sl "^4.2.3" - js-yaml "^3.5.4" - known-css-properties "^0.3.0" - lodash.capitalize "^4.1.0" - lodash.kebabcase "^4.0.0" - merge "^1.2.0" - path-is-absolute "^1.0.0" - util "^0.10.3" - sass@^1.43.4: version "1.56.0" resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.0.tgz#134032075a3223c8d49cb5c35e091e5ba1de8e0a" @@ -23886,24 +23443,12 @@ semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" + lru-cache "^6.0.0" send@0.18.0: version "0.18.0" @@ -23963,16 +23508,6 @@ serve-favicon@^2.5.0: parseurl "~1.3.2" safe-buffer "5.1.1" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - serve-static@1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" @@ -24006,11 +23541,6 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -24072,7 +23602,7 @@ shell-quote@1.7.2, shell-quote@^1.6.1, shell-quote@^1.7.3: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== -shelljs@^0.6.0, shelljs@^0.8.4, shelljs@^0.8.5: +shelljs@^0.8.4, shelljs@^0.8.5: version "0.8.5" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== @@ -24161,11 +23691,6 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= - slice-ansi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" @@ -24614,7 +24139,7 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -24948,11 +24473,6 @@ strip-json-comments@3.1.1, strip-json-comments@^3.0.1, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" - integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -25328,22 +24848,10 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.16.tgz#669b75e86b4295fdcc1bb0498de9ac1af6fd51a9" integrity sha512-qImOD23aDfnIDNqlG1NOehdB9IYsn1V9oByPjKY1nakv2MQYCEMyX033/q+aEtYCpmYK1cv2+NTmlH+ra6GA5A== -tabbable@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c" - integrity sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ== - -table@^3.7.8: - version "3.8.3" - resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" - integrity sha1-K7xULw/amGGnVdOUf+/Ys/UThV8= - dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" +tabbable@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.3.3.tgz#aac0ff88c73b22d6c3c5a50b1586310006b47fbf" + integrity sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA== table@^6.0.9, table@^6.6.0: version "6.7.1" @@ -25744,11 +25252,6 @@ toggle-selection@^1.0.6: resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -25960,7 +25463,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -26247,7 +25750,7 @@ universalify@^2.0.0: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== unquote@^1.1.0, unquote@~1.1.1: version "1.1.1" @@ -26381,13 +25884,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -user-home@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" - integrity sha1-nHC/2Babwdy/SGBODwS4tJzenp8= - dependencies: - os-homedir "^1.0.0" - username-sync@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/username-sync/-/username-sync-1.0.3.tgz#ae41c5c8a4c8c2ecc1443a7d0742742bd7e36732" @@ -26443,13 +25939,6 @@ util@0.10.3: dependencies: inherits "2.0.1" -util@^0.10.3: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== - dependencies: - inherits "2.0.3" - util@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" @@ -26465,7 +25954,7 @@ utila@~0.4: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid-browser@^3.1.0: version "3.1.0" @@ -27118,13 +26607,6 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= - dependencies: - mkdirp "^0.5.1" - ws@^6.1.0, ws@^7.4.6, ws@~7.4.2: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"