mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-11 22:01:23 +01:00
* [VAULT-37521] UI: decouple auth and secret engines * add copyright header * address acceptance test failure Co-authored-by: Shannon Roberts (Beagin) <beagins@users.noreply.github.com>
159 lines
5.4 KiB
TypeScript
159 lines
5.4 KiB
TypeScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import Component from '@glimmer/component';
|
|
import { tracked } from '@glimmer/tracking';
|
|
import { action } from '@ember/object';
|
|
import { inject as service } from '@ember/service';
|
|
import { task } from 'ember-concurrency';
|
|
import { set } from '@ember/object';
|
|
import type FlashMessagesService from 'ember-cli-flash/services/flash-messages';
|
|
import type ApiService from 'vault/services/api';
|
|
import type CapabilitiesService from 'vault/services/capabilities';
|
|
import type Router from '@ember/routing/router';
|
|
import type SecretsEngineForm from 'vault/forms/secrets/engine';
|
|
import type { ValidationMap } from 'vault/vault/app-types';
|
|
import { isAddonEngine } from 'vault/utils/all-engines-metadata';
|
|
|
|
interface Args {
|
|
model: SecretsEngineForm;
|
|
onMountSuccess?: (type: string, path: string, useEngineRoute: boolean) => void;
|
|
}
|
|
|
|
/**
|
|
* @module Mount::SecretsEngineForm
|
|
* Modern component for mounting secrets engines using the SecretsEngineForm.
|
|
*
|
|
* @example
|
|
* ```hbs
|
|
* <Mount::SecretsEngineForm @model={{this.model}} @onMountSuccess={{this.onMountSuccess}} />
|
|
* ```
|
|
*/
|
|
export default class MountSecretsEngineFormComponent extends Component<Args> {
|
|
@service declare flashMessages: FlashMessagesService;
|
|
@service declare api: ApiService;
|
|
@service declare capabilities: CapabilitiesService;
|
|
@service declare router: Router;
|
|
|
|
@tracked modelValidations: ValidationMap | null = null;
|
|
@tracked invalidFormAlert: string | null = null;
|
|
@tracked errorMessage: string | string[] = '';
|
|
|
|
get mountForm(): SecretsEngineForm {
|
|
return this.args.model;
|
|
}
|
|
|
|
@action
|
|
onKeyUp(name: string, value: string) {
|
|
set(this.mountForm.data, name, value);
|
|
}
|
|
|
|
async saveKvConfig(path: string, formData: SecretsEngineForm['data']) {
|
|
const { options, kv_config = {} } = formData;
|
|
const { max_versions, cas_required, delete_version_after } = kv_config;
|
|
const isKvV2 = options?.version === 2 && ['kv', 'generic'].includes(this.mountForm.normalizedType);
|
|
const hasConfig = max_versions || cas_required || delete_version_after;
|
|
|
|
if (isKvV2 && hasConfig) {
|
|
try {
|
|
const { canUpdate } = await this.capabilities.for('kvConfig', { path });
|
|
if (canUpdate) {
|
|
await this.api.secrets.kvV2Configure(path, kv_config);
|
|
} else {
|
|
this.flashMessages.warning(
|
|
'You do not have access to the config endpoint. The secret engine was mounted, but the configuration settings were not saved.'
|
|
);
|
|
}
|
|
} catch (e) {
|
|
const { message } = await this.api.parseError(e);
|
|
this.flashMessages.warning(
|
|
`The secret engine was mounted, but the configuration settings were not saved. ${message}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async onMountError(status: number, errors: unknown[] | undefined, message: string) {
|
|
if (status === 403) {
|
|
this.flashMessages.danger(
|
|
'You do not have access to the sys/mounts endpoint. The secret engine was not mounted.'
|
|
);
|
|
} else if (errors) {
|
|
this.errorMessage = errors.map((e) => {
|
|
if (typeof e === 'object' && e !== null) {
|
|
const errorObj = e as { title?: string; message?: string };
|
|
return errorObj.title || errorObj.message || JSON.stringify(e);
|
|
}
|
|
return String(e);
|
|
});
|
|
} else if (message) {
|
|
this.errorMessage = message;
|
|
} else {
|
|
this.errorMessage = 'An error occurred, check the vault logs.';
|
|
}
|
|
}
|
|
|
|
@task
|
|
*mountBackend(event: Event) {
|
|
event.preventDefault();
|
|
const mountModel = this.mountForm;
|
|
const { type } = mountModel;
|
|
const { path } = mountModel.data;
|
|
|
|
// Only submit form if validations pass
|
|
const { isValid, state, invalidFormMessage, data } = mountModel.toJSON();
|
|
if (!isValid) {
|
|
this.modelValidations = state;
|
|
this.invalidFormAlert = invalidFormMessage;
|
|
return;
|
|
}
|
|
|
|
this.errorMessage = '';
|
|
this.modelValidations = null;
|
|
this.invalidFormAlert = null;
|
|
|
|
try {
|
|
// Mount the secrets engine
|
|
yield this.api.sys.mountsEnableSecretsEngine(path, data);
|
|
|
|
// Save KV config if applicable
|
|
yield this.saveKvConfig(path, data);
|
|
|
|
this.flashMessages.success(`Successfully mounted the ${mountModel.type} secrets engine at ${path}.`);
|
|
|
|
// Determine if we should use engine routes
|
|
const version = data.options?.version;
|
|
const useEngineRoute = isAddonEngine(mountModel.normalizedType, Number(version));
|
|
|
|
// Call success callback or navigate
|
|
if (this.args.onMountSuccess) {
|
|
this.args.onMountSuccess(type, path, useEngineRoute);
|
|
} else {
|
|
// Default navigation
|
|
if (useEngineRoute) {
|
|
this.router.transitionTo('vault.cluster.secrets.backend.index', path);
|
|
} else {
|
|
this.router.transitionTo('vault.cluster.secrets.backend.list-root', path);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
const { status, response, message } = yield this.api.parseError(error);
|
|
this.onMountError(status, response.errors, message);
|
|
}
|
|
}
|
|
|
|
@action
|
|
handleIdentityTokenKeyChange(value: string[] | string): void {
|
|
// if array, it's coming from the search-select component, otherwise it hit the fallback component and will come in as a string.
|
|
const { config } = this.mountForm.data;
|
|
config.identity_token_key = Array.isArray(value) ? value[0] : value;
|
|
}
|
|
|
|
@action
|
|
goBack() {
|
|
this.router.transitionTo('vault.cluster.secrets.mounts');
|
|
}
|
|
}
|