mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
* adds mirage factories for mfa methods and login enforcement * adds mirage handler for mfa config endpoints * adds mirage identity manager for uuids * updates mfa test to use renamed mfaLogin mirage handler * updates mfa login workflow for push methods (#15214) * MFA Login Enforcement Model (#15244) * adds mfa login enforcement model, adapter and serializer * updates mfa methods to hasMany realtionship and transforms property names * updates login enforcement adapter to use urlForQuery over buildURL * Model for mfa method (#15218) * Model for mfa method * Added adapter and serializer for mfa method - Updated mfa method model - Basic route to handle list view - Added MFA to access nav * Show landing page if methods are not configured * Updated adapter,serializer - Backend is adding new endpoint to list all the mfa methods * Updated landing page - Added MFA diagram - Created helper to resolve full path for assets like images * Remove ember assign * Fixed failing test * MFA method and enforcement list view (#15353) * MFA method and enforcement list view - Added new route for list views - List mfa methods along with id, type and icon - Added client side pagination to list views * Throw error if method id is not present * MFA Login Enforcement Form (#15410) * adds mfa login enforcement form and header components and radio card component * skips login enforcement form tests for now * adds jsdoc annotations for mfa-login-enforcement-header component * adds error handling when fetching identity targets in login enforcement form component * updates radio-card label elements * MFA Login Enforcement Create and Edit routes (#15422) * adds mfa login enforcement form and header components and radio card component * skips login enforcement form tests for now * updates to login enforcement form to fix issues hydrating methods and targets from model when editing * updates to mfa-config mirage handler and login enforcement handler * fixes issue with login enforcement serializer normalizeItems method throwing error on save * updates to mfa route structure * adds login enforcement create and edit routes * MFA Login Enforcement Read Views (#15462) * adds login enforcement read views * skip mfa-method-list-item test for now * MFA method form (#15432) * MFA method form - Updated model for form attributes - Form for editing, creating mfa methods * Added comments * Update model for mfa method * Refactor buildURL in mfa method adapter * Update adapter to handle mfa create * Fixed adapter to handle create mfa response * Sidebranch: MFA end user setup (#15273) * initial setup of components and route * fix navbar * replace parent component with controller * use auth service to return entity id * adapter and some error handling: * clean up adapter and handle warning * wip * use library for qrCode generation * clear warning and QR code display fix * flow for restart setup * add documentation * clean up * fix warning issue * handle root user * remove comment * update copy * fix margin * address comment * MFA Guided Setup Route (#15479) * adds mfa method create route with type selection workflow * updates mfa method create route links to use DocLink component * MFA Guided Setup Config View (#15486) * adds mfa guided setup config view * resets type query param on mfa method create route exit * hide next button if type is not selected in mfa method create route * updates to sure correct state when changing mfa method type in guided setup * Enforcement view at MFA method level (#15485) - List enforcements for each mfa method - Delete MFA method if no enforcements are present - Moved method, enforcement list item component to mfa folder * MFA Login Enforcement Validations (#15498) * adds model and form validations for mfa login enforcements * updates mfa login enforcement validation messages * updates validation message for mfa login enforcement targets * adds transition action to configure mfa button on landing page * unset enforcement on preference change in mfa guided setup workflow * Added validations for mfa method model (#15506) * UI/mfa breadcrumbs and small fixes (#15499) * add active class when on index * breadcrumbs * remove box-shadow to match designs * fix refresh load mfa-method * breadcrumb create * add an empty state the enforcements list view * change to beforeModel * UI/mfa small bugs (#15522) * remove pagintion and fix on methods list view * fix enforcements * Fix label for value on radio-card (#15542) * MFA Login Enforcement Component Tests (#15539) * adds tests for mfa-login-enforcement-header component * adds tests for mfa-login-enforcement-form component * Remove default values from mfa method model (#15540) - use passcode had a default value, as a result it was being sent with all the mfa method types during save and edit flows.. * UI/mfa small cleanup (#15549) * data-test-mleh -> data-test-mfa * Only one label per radio card * Remove unnecessary async * Simplify boolean logic * Make mutation clear * Revert "data-test-mleh -> data-test-mfa" This reverts commit 31430df7bb42580a976d082667cb6ed1f09c3944. * updates mfa login enforcement form to only display auth method types for current mounts as targets (#15547) * remove token type (#15548) * remove token type * conditional param * removes type from mfa method payload and fixes bug transitioning to method route on save success * removes punctuation from mfa form error message string match * updates qr-code component invocation to angle bracket * Re-trigger CI jobs with empty commit Co-authored-by: Arnav Palnitkar <arnav@hashicorp.com> Co-authored-by: Angel Garbarino <Monkeychip@users.noreply.github.com> Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> Co-authored-by: Michele Degges <mdeggies@gmail.com>
219 lines
8.4 KiB
JavaScript
219 lines
8.4 KiB
JavaScript
import Component from '@ember/component';
|
|
import { inject as service } from '@ember/service';
|
|
import { task } from 'ember-concurrency';
|
|
import { computed } from '@ember/object';
|
|
import { singularize } from 'ember-inflector';
|
|
import { resolve } from 'rsvp';
|
|
import { filterOptions, defaultMatcher } from 'ember-power-select/utils/group-utils';
|
|
import layout from '../templates/components/search-select';
|
|
|
|
/**
|
|
* @module SearchSelect
|
|
* The `SearchSelect` is an implementation of the [ember-power-select](https://github.com/cibernox/ember-power-select) used for form elements where options come dynamically from the API.
|
|
* @example
|
|
* <SearchSelect @id="group-policies" @models={{["policies/acl"]}} @onChange={{onChange}} @selectLimit={{2}} @inputValue={{get model valuePath}} @helpText="Policies associated with this group" @label="Policies" @fallbackComponent="string-list" />
|
|
*
|
|
* @param {string} id - The name of the form field
|
|
* @param {Array} models - An array of model types to fetch from the API.
|
|
* @param {function} onChange - The onchange action for this form field.
|
|
* @param {string | Array} inputValue - A comma-separated string or an array of strings -- array of ids for models.
|
|
* @param {string} label - Label for this form field
|
|
* @param {string} fallbackComponent - name of component to be rendered if the API call 403s
|
|
* @param {string} [backend] - name of the backend if the query for options needs additional information (eg. secret backend)
|
|
* @param {boolean} [disallowNewItems=false] - Controls whether or not the user can add a new item if none found
|
|
* @param {boolean} [passObject=false] - When true, the onChange callback returns an array of objects with id (string) and isNew (boolean)
|
|
* @param {string} [helpText] - Text to be displayed in the info tooltip for this form field
|
|
* @param {number} [selectLimit] - A number that sets the limit to how many select options they can choose
|
|
* @param {string} [subText] - Text to be displayed below the label
|
|
* @param {string} [subLabel] - a smaller label below the main Label
|
|
* @param {string} [wildcardLabel] - when you want the searchSelect component to return a count on the model for options returned when using a wildcard you must provide a label of the count e.g. role. Should be singular.
|
|
* @param {string} [placeholder] - text you wish to replace the default "search" with
|
|
* @param {boolean} [displayInherit] - if you need the search select component to display inherit instead of box.
|
|
*
|
|
* @param {Array} options - *Advanced usage* - `options` can be passed directly from the outside to the
|
|
* power-select component. If doing this, `models` should not also be passed as that will overwrite the
|
|
* passed value. ex: [{ name: 'namespace45', id: 'displayedName' }];
|
|
* @param {function} search - *Advanced usage* - Customizes how the power-select component searches for matches -
|
|
* see the power-select docs for more information.
|
|
*
|
|
*/
|
|
export default Component.extend({
|
|
layout,
|
|
'data-test-component': 'search-select',
|
|
attributeBindings: ['data-test-component'],
|
|
classNameBindings: ['displayInherit:display-inherit'],
|
|
classNames: ['field', 'search-select'],
|
|
store: service(),
|
|
|
|
onChange: () => {},
|
|
inputValue: computed(function () {
|
|
return [];
|
|
}),
|
|
allOptions: null, // list of options including matched
|
|
selectedOptions: null, // list of selected options
|
|
options: null, // all possible options
|
|
shouldUseFallback: false,
|
|
shouldRenderName: false,
|
|
disallowNewItems: false,
|
|
passObject: false,
|
|
|
|
init() {
|
|
this._super(...arguments);
|
|
this.set('selectedOptions', this.inputValue || []);
|
|
},
|
|
didRender() {
|
|
this._super(...arguments);
|
|
let { oldOptions, options, selectedOptions } = this;
|
|
let hasFormattedInput = typeof selectedOptions.firstObject !== 'string';
|
|
if (options && !oldOptions && !hasFormattedInput) {
|
|
// this is the first time they've been set, so we need to format them
|
|
this.formatOptions(options);
|
|
}
|
|
this.set('oldOptions', options);
|
|
},
|
|
formatOptions: function (options) {
|
|
options = options.toArray().map((option) => {
|
|
option.searchText = `${option.name} ${option.id}`;
|
|
return option;
|
|
});
|
|
let allOptions = options.toArray().map((option) => {
|
|
return option.id;
|
|
});
|
|
this.set('allOptions', allOptions); // used by filter-wildcard helper
|
|
let formattedOptions = this.selectedOptions.map((option) => {
|
|
let matchingOption = options.findBy('id', option);
|
|
options.removeObject(matchingOption);
|
|
return {
|
|
id: option,
|
|
name: matchingOption ? matchingOption.name : option,
|
|
searchText: matchingOption ? matchingOption.searchText : option,
|
|
};
|
|
});
|
|
|
|
this.set('selectedOptions', formattedOptions);
|
|
if (this.options) {
|
|
options = this.options.concat(options).uniq();
|
|
}
|
|
this.set('options', options);
|
|
},
|
|
fetchOptions: task(function* () {
|
|
if (!this.models) {
|
|
if (this.options) {
|
|
this.formatOptions(this.options);
|
|
}
|
|
return;
|
|
}
|
|
for (let modelType of this.models) {
|
|
if (modelType.includes('identity')) {
|
|
this.set('shouldRenderName', true);
|
|
}
|
|
try {
|
|
let queryOptions = {};
|
|
if (this.backend) {
|
|
queryOptions = { backend: this.backend };
|
|
}
|
|
let options = yield this.store.query(modelType, queryOptions);
|
|
this.formatOptions(options);
|
|
} catch (err) {
|
|
if (err.httpStatus === 404) {
|
|
if (!this.options) {
|
|
// If the call failed but the resource has items
|
|
// from a different namespace, this allows the
|
|
// selected items to display
|
|
this.set('options', []);
|
|
}
|
|
|
|
return;
|
|
}
|
|
if (err.httpStatus === 403) {
|
|
this.set('shouldUseFallback', true);
|
|
return;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
}).on('didInsertElement'),
|
|
handleChange() {
|
|
if (this.selectedOptions.length && typeof this.selectedOptions.firstObject === 'object') {
|
|
if (this.passObject) {
|
|
this.onChange(Array.from(this.selectedOptions, (option) => ({ id: option.id, isNew: !!option.new })));
|
|
} else {
|
|
this.onChange(Array.from(this.selectedOptions, (option) => option.id));
|
|
}
|
|
} else {
|
|
this.onChange(this.selectedOptions);
|
|
}
|
|
},
|
|
shouldShowCreate(id, options) {
|
|
if (options && options.length && options.firstObject.groupName) {
|
|
return !options.some((group) => group.options.findBy('id', id));
|
|
}
|
|
let existingOption = this.options && (this.options.findBy('id', id) || this.options.findBy('name', id));
|
|
if (this.disallowNewItems && !existingOption) {
|
|
return false;
|
|
}
|
|
return !existingOption;
|
|
},
|
|
//----- adapted from ember-power-select-with-create
|
|
addCreateOption(term, results) {
|
|
if (this.shouldShowCreate(term, results)) {
|
|
const name = `Add new ${singularize(this.label)}: ${term}`;
|
|
const suggestion = {
|
|
__isSuggestion__: true,
|
|
__value__: term,
|
|
name,
|
|
id: name,
|
|
};
|
|
results.unshift(suggestion);
|
|
}
|
|
},
|
|
filter(options, searchText) {
|
|
const matcher = (option, text) => defaultMatcher(option.searchText, text);
|
|
return filterOptions(options || [], searchText, matcher);
|
|
},
|
|
// -----
|
|
|
|
actions: {
|
|
onChange(val) {
|
|
this.onChange(val);
|
|
},
|
|
discardSelection(selected) {
|
|
this.selectedOptions.removeObject(selected);
|
|
// fire off getSelectedValue action higher up in get-credentials-card component
|
|
if (!selected.new) {
|
|
this.options.pushObject(selected);
|
|
}
|
|
this.handleChange();
|
|
},
|
|
// ----- adapted from ember-power-select-with-create
|
|
searchAndSuggest(term, select) {
|
|
if (term.length === 0) {
|
|
return this.options;
|
|
}
|
|
if (this.search) {
|
|
return resolve(this.search(term, select)).then((results) => {
|
|
if (results.toArray) {
|
|
results = results.toArray();
|
|
}
|
|
this.addCreateOption(term, results);
|
|
return results;
|
|
});
|
|
}
|
|
const newOptions = this.filter(this.options, term);
|
|
this.addCreateOption(term, newOptions);
|
|
return newOptions;
|
|
},
|
|
selectOrCreate(selection) {
|
|
if (selection && selection.__isSuggestion__) {
|
|
const name = selection.__value__;
|
|
this.selectedOptions.pushObject({ name, id: name, new: true });
|
|
} else {
|
|
this.selectedOptions.pushObject(selection);
|
|
this.options.removeObject(selection);
|
|
}
|
|
this.handleChange();
|
|
},
|
|
// -----
|
|
},
|
|
});
|