diff --git a/ui/app/adapters/generated-item-list.js b/ui/app/adapters/generated-item-list.js index 2d2efd8a9e..c6e268351c 100644 --- a/ui/app/adapters/generated-item-list.js +++ b/ui/app/adapters/generated-item-list.js @@ -8,6 +8,7 @@ import { service } from '@ember/service'; import { sanitizePath } from 'core/utils/sanitize-path'; import { encodePath } from 'vault/utils/path-encoding-helpers'; import { tracked } from '@glimmer/tracking'; +import { getOwner } from '@ember/owner'; export default class GeneratedItemListAdapter extends ApplicationAdapter { @service store; @@ -28,8 +29,10 @@ export default class GeneratedItemListAdapter extends ApplicationAdapter { return this.paths.deletePath || ''; } - getDynamicApiPath(id) { - const result = this.store.peekRecord('auth-method', id); + getDynamicApiPath() { + const result = getOwner(this) + .lookup('route:vault.cluster.access.method') + .modelFor('vault.cluster.access.method'); this.apiPath = result.apiPath; return result.apiPath; } diff --git a/ui/app/components/auth-method/configuration.hbs b/ui/app/components/auth-method/configuration.hbs new file mode 100644 index 0000000000..afdf80a498 --- /dev/null +++ b/ui/app/components/auth-method/configuration.hbs @@ -0,0 +1,20 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + +
+ {{#if @method.directLoginLink}} + + + + {{/if}} + {{#each this.displayFields as |field|}} + + {{/each}} +
\ No newline at end of file diff --git a/ui/app/components/auth-method/configuration.ts b/ui/app/components/auth-method/configuration.ts new file mode 100644 index 0000000000..d259f7f678 --- /dev/null +++ b/ui/app/components/auth-method/configuration.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { toLabel } from 'core/helpers/to-label'; +import { get } from '@ember/object'; + +import type AuthMethodResource from 'vault/resources/auth/method'; + +interface Args { + method: AuthMethodResource; +} +export default class AuthMethodConfigurationComponent extends Component { + displayFields = [ + 'type', + 'path', + 'description', + 'accessor', + 'local', + 'sealWrap', + 'config.listingVisibility', + 'config.defaultLeaseTtl', + 'config.maxLeaseTtl', + 'config.tokenType', + 'config.auditNonHmacRequestKeys', + 'config.auditNonHmacResponseKeys', + 'config.passthroughRequestHeaders', + 'config.allowedResponseHeaders', + 'config.pluginVersion', + ]; + + label = (field: string) => { + const key = field.replace('config.', ''); + const label = toLabel([key]); + // map specific fields to custom labels + return ( + { + listingVisibility: 'Use as preferred UI login method', + defaultLeaseTtl: 'Default Lease TTL', + maxLeaseTtl: 'Max Lease TTL', + auditNonHmacRequestKeys: 'Request keys excluded from HMACing in audit', + auditNonHmacResponseKeys: 'Response keys excluded from HMACing in audit', + passthroughRequestHeaders: 'Allowed passthrough request headers', + }[key] || label + ); + }; + value = (field: string) => { + const { method } = this.args; + if (field === 'config.listingVisibility') { + return method.config.listingVisibility === 'unauth'; + } + return get(method, field); + }; + + isTtl = (field: string) => { + return ['config.defaultLeaseTtl', 'config.maxLeaseTtl'].includes(field); + }; +} diff --git a/ui/app/controllers/vault/cluster/access/methods.js b/ui/app/controllers/vault/cluster/access/methods.js index 9cb6ad995e..d73bb3fe85 100644 --- a/ui/app/controllers/vault/cluster/access/methods.js +++ b/ui/app/controllers/vault/cluster/access/methods.js @@ -26,23 +26,24 @@ export default class VaultClusterAccessMethodsController extends Controller { // list returned by getter is sorted in template get authMethodList() { + const { methods } = this.model; // return an options list to filter by engine type, ex: 'kv' if (this.selectedAuthType) { // check first if the user has also filtered by name. // names are individualized across type so you can't have the same name for an aws auth method and userpass. // this means it's fine to filter by first type and then name or just name. if (this.selectedAuthName) { - return this.model.filter((method) => this.selectedAuthName === method.id); + return methods.filter((method) => this.selectedAuthName === method.id); } // otherwise filter by auth type - return this.model.filter((method) => this.selectedAuthType === method.type); + return methods.filter((method) => this.selectedAuthType === method.type); } // return an options list to filter by auth name, ex: 'my-userpass' if (this.selectedAuthName) { - return this.model.filter((method) => this.selectedAuthName === method.id); + return methods.filter((method) => this.selectedAuthName === method.id); } // no filters, return full list - return this.model; + return methods; } get authMethodArrayByType() { diff --git a/ui/app/resources/auth/method.ts b/ui/app/resources/auth/method.ts new file mode 100644 index 0000000000..d01f5b5aa8 --- /dev/null +++ b/ui/app/resources/auth/method.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { baseResourceFactory } from 'vault/resources/base-factory'; +import { service } from '@ember/service'; +import { supportedTypes } from 'vault/utils/supported-login-methods'; +import engineDisplayData from 'vault/helpers/engines-display-data'; + +import type { Mount } from 'vault/mount'; +import type VersionService from 'vault/services/version'; +import type NamespaceService from 'vault/services/namespace'; +import type { PathInfo } from 'vault/utils/openapi-helpers'; + +export default class AuthMethodResource extends baseResourceFactory() { + @service declare readonly version: VersionService; + @service declare readonly namespace: NamespaceService; + + id: string; + declare paths: PathInfo; + + constructor(data: Mount, context: unknown) { + super(data, context); + // strip trailing slash from path for id since it is used in routing + this.id = data.path.replace(/\/$/, ''); + } + + // namespaces introduced types with a `ns_` prefix for built-in engines + // so we need to strip that to normalize the type + get methodType() { + return this.type.replace(/^ns_/, ''); + } + + get icon() { + // methodType refers to the backend type (e.g., "aws", "azure") + const engineData = engineDisplayData(this.methodType); + return engineData?.glyph || 'users'; + } + + get directLoginLink() { + const ns = this.namespace.path; + const nsQueryParam = ns ? `namespace=${encodeURIComponent(ns)}&` : ''; + const isSupported = supportedTypes(this.version.isEnterprise).includes(this.methodType); + return isSupported + ? `${window.origin}/ui/vault/auth?${nsQueryParam}with=${encodeURIComponent(this.path)}` + : ''; + } + + // used when the `auth` prefix is important, + // currently only when setting perf mount filtering + get apiPath() { + return `auth/${this.path}`; + } + + get localDisplay() { + return this.local ? 'local' : 'replicated'; + } + + get supportsUserLockoutConfig() { + return ['approle', 'ldap', 'userpass'].includes(this.methodType); + } +} diff --git a/ui/app/resources/base-factory.ts b/ui/app/resources/base-factory.ts index 9e6a722b14..e667aa5dc8 100644 --- a/ui/app/resources/base-factory.ts +++ b/ui/app/resources/base-factory.ts @@ -3,18 +3,27 @@ * SPDX-License-Identifier: BUSL-1.1 */ +import { getOwner, setOwner } from '@ember/owner'; + +import type Owner from '@ember/owner'; + abstract class BaseResource { // pass data that the resource should represent (typically from an API response) to constructor // object properties will be assigned to class instance // extending classes can define getters and additional properties/methods that are required widely across the app - constructor(readonly data: T) { + constructor(data: T, context?: unknown) { Object.assign(this, data) as T; + // pass in context (this) of Ember class (route, component etc.) where the resource is being constructed + // this will be used to set the owner on the class so that services can be injected (if required) + if (context) { + setOwner(this, getOwner(context) as Owner); + } } } -// factory that allows for the BaseResource class to be casted to the specific type provided +// factory that allows for the BaseResource class to be cast to the specific type provided // without this the compiler is not aware of the properties set on the class via Object.assign -// example usage -> export default class SecretsEngineResource extends baseResourceFactory() { ... } +// example usage -> export default class SecretsEngineResource extends baseResourceFactory() { ... } export function baseResourceFactory() { - return BaseResource as new (data: T) => T; + return BaseResource as new (data: T, context?: unknown) => T; } diff --git a/ui/app/resources/secrets/engine.ts b/ui/app/resources/secrets/engine.ts index b975f39d4f..e9e6dc62f0 100644 --- a/ui/app/resources/secrets/engine.ts +++ b/ui/app/resources/secrets/engine.ts @@ -8,14 +8,14 @@ import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends import { isAddonEngine } from 'vault/utils/all-engines-metadata'; import engineDisplayData from 'vault/helpers/engines-display-data'; -import type { SecretsEngine } from 'vault/secrets/engine'; +import type { Mount } from 'vault/mount'; -export default class SecretsEngineResource extends baseResourceFactory() { +export default class SecretsEngineResource extends baseResourceFactory() { id: string; #LIST_EXCLUDED_BACKENDS = ['system', 'identity']; - constructor(data: SecretsEngine) { + constructor(data: Mount) { super(data); // strip trailing slash from path for id since it is used in routing this.id = data.path.replace(/\/$/, ''); diff --git a/ui/app/routes/vault/cluster/access/method.js b/ui/app/routes/vault/cluster/access/method.js deleted file mode 100644 index a102b9c3df..0000000000 --- a/ui/app/routes/vault/cluster/access/method.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import AdapterError from '@ember-data/adapter/error'; -import { set } from '@ember/object'; -import Route from '@ember/routing/route'; -import { service } from '@ember/service'; -import { supportedManagedAuthBackends } from 'vault/helpers/supported-managed-auth-backends'; - -export default Route.extend({ - store: service(), - pathHelp: service('path-help'), - - model(params) { - const { path } = params; - return this.store.query('auth-method', {}).then((modelArray) => { - const model = modelArray.find((m) => m.id === path); - if (!model) { - const error = new AdapterError(); - set(error, 'httpStatus', 404); - throw error; - } - const supportManaged = supportedManagedAuthBackends(); - if (!supportManaged.includes(model.methodType)) { - // do not fetch path-help for unmanaged auth types - model.set('paths', { - apiPath: model.apiPath, - paths: [], - }); - return model; - } - return this.pathHelp.getPaths(model.apiPath, path).then((paths) => { - model.set('paths', paths); - return model; - }); - }); - }, -}); diff --git a/ui/app/routes/vault/cluster/access/method.ts b/ui/app/routes/vault/cluster/access/method.ts new file mode 100644 index 0000000000..143d8b6136 --- /dev/null +++ b/ui/app/routes/vault/cluster/access/method.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Route from '@ember/routing/route'; +import { service } from '@ember/service'; +import { supportedManagedAuthBackends } from 'vault/helpers/supported-managed-auth-backends'; +import AuthMethodResource from 'vault/resources/auth/method'; + +import type ApiService from 'vault/services/api'; +import type PathHelpService from 'vault/services/path-help'; + +export default class VaultClusterAccessMethodRoute extends Route { + @service declare readonly api: ApiService; + @service declare readonly pathHelp: PathHelpService; + + async model(params: { path: string }) { + const { path } = params; + const { auth } = await this.api.sys.internalUiListEnabledVisibleMounts(); + const methods = this.api + .responseObjectToArray(auth, 'path') + .map((method) => new AuthMethodResource(method, this)); + const method = methods.find((m) => m.id === path); + // the user could have entered a random path in the URL that doesn't correspond to an existing method + if (method) { + const supportManaged = supportedManagedAuthBackends(); + // do not fetch path-help for unmanaged auth types + if (!supportManaged.includes(method.methodType)) { + method.paths = { apiPath: method.apiPath, paths: [], itemTypes: [] }; + return method; + } + return this.pathHelp.getPaths(method.apiPath, path, '', '').then((pathInfo) => { + method.paths = pathInfo; + return method; + }); + } else { + // throw a 404 if the path doesn't match any of the fetched methods + throw { httpStatus: 404, path }; + } + } +} diff --git a/ui/app/routes/vault/cluster/access/method/section.js b/ui/app/routes/vault/cluster/access/method/section.js index 7533f7fda4..af0c3008e9 100644 --- a/ui/app/routes/vault/cluster/access/method/section.js +++ b/ui/app/routes/vault/cluster/access/method/section.js @@ -18,14 +18,13 @@ export default Route.extend({ return this.modelFor('vault.cluster.access.method'); }, - setupController(controller) { + setupController(controller, model) { const { section_name: section } = this.paramsFor(this.routeName); this._super(...arguments); controller.set('section', section); - const method = this.modelFor('vault.cluster.access.method'); controller.set( 'paths', - method.paths.paths.filter((path) => path.navigation) + model.paths.paths.filter((path) => path.navigation) ); }, }); diff --git a/ui/app/routes/vault/cluster/access/methods.js b/ui/app/routes/vault/cluster/access/methods.js deleted file mode 100644 index e522e7e14b..0000000000 --- a/ui/app/routes/vault/cluster/access/methods.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Route from '@ember/routing/route'; -import { service } from '@ember/service'; - -export default class VaultClusterAccessMethodsRoute extends Route { - @service store; - - queryParams = { - page: { - refreshModel: true, - }, - pageFilter: { - refreshModel: true, - }, - }; - - model() { - return this.store.query('auth-method', {}); - } -} diff --git a/ui/app/routes/vault/cluster/access/methods.ts b/ui/app/routes/vault/cluster/access/methods.ts new file mode 100644 index 0000000000..67f57ba8bc --- /dev/null +++ b/ui/app/routes/vault/cluster/access/methods.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Route from '@ember/routing/route'; +import { service } from '@ember/service'; +import AuthMethodResource from 'vault/resources/auth/method'; + +import type ApiService from 'vault/services/api'; +import type Capabilities from 'vault/services/capabilities'; + +export default class VaultClusterAccessMethodsRoute extends Route { + @service declare readonly api: ApiService; + @service declare readonly capabilities: Capabilities; + + queryParams = { + page: { + refreshModel: true, + }, + pageFilter: { + refreshModel: true, + }, + }; + + async model() { + const { auth } = await this.api.sys.internalUiListEnabledVisibleMounts(); + + const methods = this.api + .responseObjectToArray(auth, 'path') + .map((method) => new AuthMethodResource(method, this)); + + const paths = methods.reduce((paths: string[], { path, methodType }) => { + paths.push( + this.capabilities.pathFor('authMethodConfig', { path }), + this.capabilities.pathFor('authMethodDelete', { path }) + ); + if (methodType === 'aws') { + paths.push(this.capabilities.pathFor('authMethodConfigAws', { path })); + } + return paths; + }, []); + + const capabilities = this.capabilities.fetch(paths); + + return { methods, capabilities }; + } +} diff --git a/ui/app/templates/components/auth-method/configuration.hbs b/ui/app/templates/components/auth-method/configuration.hbs deleted file mode 100644 index 7ed94c5b24..0000000000 --- a/ui/app/templates/components/auth-method/configuration.hbs +++ /dev/null @@ -1,29 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -
- {{#if @model.directLoginLink}} - - - - {{/if}} - {{#each @model.attrs as |attr|}} - {{#if (eq attr.type "object")}} - - {{else}} - - {{/if}} - {{/each}} -
\ No newline at end of file diff --git a/ui/app/templates/vault/cluster/access/method/section.hbs b/ui/app/templates/vault/cluster/access/method/section.hbs index 08799aadf9..df721c0244 100644 --- a/ui/app/templates/vault/cluster/access/method/section.hbs +++ b/ui/app/templates/vault/cluster/access/method/section.hbs @@ -36,4 +36,4 @@ {{/if}} -{{component (concat "auth-method/" this.section) model=this.model}} \ No newline at end of file +{{component (concat "auth-method/" this.section) method=this.model}} \ No newline at end of file diff --git a/ui/app/templates/vault/cluster/access/methods.hbs b/ui/app/templates/vault/cluster/access/methods.hbs index da7100ab9a..f68da22694 100644 --- a/ui/app/templates/vault/cluster/access/methods.hbs +++ b/ui/app/templates/vault/cluster/access/methods.hbs @@ -83,16 +83,28 @@ View configuration - {{#if (or method.canEdit (and (eq method.methodType "aws") method.canEditAws))}} + {{#if + (or + (has-capability this.model.capabilities "update" pathKey="authMethodConfig" params=method.id) + (and + (eq method.methodType "aws") + (has-capability this.model.capabilities "update" pathKey="authMethodConfigAws" params=method.id) + ) + ) + }} Edit configuration {{/if}} - {{#if (and (not-eq method.methodType "token") method.canDisable)}} - Disable + {{#if + (and + (not-eq method.methodType "token") + (has-capability this.model.capabilities "delete" pathKey="authMethodDelete" params=method.id) + ) + }} + + Disable + {{/if}} diff --git a/ui/app/utils/constants/capabilities.ts b/ui/app/utils/constants/capabilities.ts index 6974765312..c4cfe46069 100644 --- a/ui/app/utils/constants/capabilities.ts +++ b/ui/app/utils/constants/capabilities.ts @@ -23,4 +23,7 @@ export const PATH_MAP = { syncSetAssociation: apiPath`sys/sync/destinations/${'type'}/${'name'}/associations/set`, syncRemoveAssociation: apiPath`sys/sync/destinations/${'type'}/${'name'}/associations/remove`, kvConfig: apiPath`${'path'}/config`, + authMethodConfig: apiPath`auth/${'path'}/config`, + authMethodConfigAws: apiPath`auth/${'path'}/config/client`, + authMethodDelete: apiPath`sys/auth/${'path'}`, }; diff --git a/ui/app/utils/openapi-helpers.ts b/ui/app/utils/openapi-helpers.ts index 91675bf580..91b2883e87 100644 --- a/ui/app/utils/openapi-helpers.ts +++ b/ui/app/utils/openapi-helpers.ts @@ -16,12 +16,13 @@ interface Path { navigation: boolean; param: string | false; } -interface PathsInfo { +export type PathInfo = { apiPath: string; - itemType: string; - itemTypes: string[]; paths: Path[]; -} + itemTypes: string[]; + itemType?: string; + itemID?: string; +}; interface OpenApiParameter { description?: string; @@ -53,7 +54,7 @@ interface OpenApiPath { } // Take object entries from the OpenAPI response and consolidate them into an object which includes itemTypes, operations, and paths -export function reducePathsByPathName(pathsInfo: PathsInfo, currentPath: [string, OpenApiPath]): PathsInfo { +export function reducePathsByPathName(pathsInfo: PathInfo, currentPath: [string, OpenApiPath]): PathInfo { const pathName = currentPath[0]; const pathDetails = currentPath[1]; const displayAttrs = pathDetails['x-vault-displayAttrs']; @@ -123,7 +124,7 @@ export function pathToHelpUrlSegment(path: string): string { return path.replaceAll(apiPathRegex, 'example'); } -export function filterPathsByItemType(pathInfo: PathsInfo, itemType: string): Path[] { +export function filterPathsByItemType(pathInfo: PathInfo, itemType: string): Path[] { if (!itemType) { return pathInfo.paths; } diff --git a/ui/tests/integration/components/auth-method/configuration-test.js b/ui/tests/integration/components/auth-method/configuration-test.js index 76b4f80bed..e76bba109b 100644 --- a/ui/tests/integration/components/auth-method/configuration-test.js +++ b/ui/tests/integration/components/auth-method/configuration-test.js @@ -8,34 +8,34 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; +import AuthMethodResource from 'vault/resources/auth/method'; module('Integration | Component | auth-method/configuration', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { this.store = this.owner.lookup('service:store'); - this.createModel = (path, type) => { - this.model = this.store.createRecord('auth-method', { path, type }); - this.model.set('config', this.store.createRecord('mount-config')); + this.createMethod = (path, type) => { + this.method = new AuthMethodResource({ path, type, config: { listingVisibility: 'hidden' } }, this); }; - this.renderComponent = async () => await render(hbs``); + this.renderComponent = () => render(hbs``); }); test('it renders direct link for supported method', async function (assert) { - this.createModel('token/', 'token'); + this.createMethod('token/', 'token'); await this.renderComponent(); assert.dom(GENERAL.infoRowValue('UI login link')).hasText(`${window.origin}/ui/vault/auth?with=token%2F`); }); test('it does not render direct link for unsupported method', async function (assert) { - this.createModel('my-approle/', 'approle'); + this.createMethod('my-approle/', 'approle'); await this.renderComponent(); assert.dom(GENERAL.infoRowValue('UI login link')).doesNotExist(); }); test('it renders direct link if within a namespace', async function (assert) { this.owner.lookup('service:namespace').set('path', 'foo/bar'); - this.createModel('token/', 'token'); + this.createMethod('token/', 'token'); await this.renderComponent(); assert .dom(GENERAL.infoRowValue('UI login link')) diff --git a/ui/types/vault/mount.d.ts b/ui/types/vault/mount.d.ts new file mode 100644 index 0000000000..12fe15f599 --- /dev/null +++ b/ui/types/vault/mount.d.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +export type MountConfig = { + forceNoCache?: boolean; + listingVisibility?: string | boolean; + defaultLeaseTtl?: number; + maxLeaseTtl?: number; + allowedManagedKeys?: string[]; + auditNonHmacRequestKeys?: string[]; + auditNonHmacResponseKeys?: string[]; + passthroughRequestHeaders?: string[]; + allowedResponseHeaders?: string[]; + identityTokenKey?: string; +}; + +export type MountOptions = { + version: number; +}; + +export type Mount = { + path: string; + accessor: string; + config: MountConfig; + description: string; + externalEntropyAccess: boolean; + local: boolean; + options?: MountOptions; + pluginVersion: string; + runningPluginVersion: string; + runningSha256: string; + sealWrap: boolean; + type: string; + uuid: string; +}; diff --git a/ui/types/vault/secrets/engine.d.ts b/ui/types/vault/secrets/engine.d.ts index b1f6d28051..1cb4cfd2d1 100644 --- a/ui/types/vault/secrets/engine.d.ts +++ b/ui/types/vault/secrets/engine.d.ts @@ -10,39 +10,7 @@ import type { AzureConfigureRequest, GoogleCloudConfigureRequest, } from '@hashicorp/vault-client-typescript'; - -export type EngineConfig = { - forceNoCache?: boolean; - listingVisibility?: string | boolean; - defaultLeaseTtl?: number; - maxLeaseTtl?: number; - allowedManagedKeys?: string[]; - auditNonHmacRequestKeys?: string[]; - auditNonHmacResponseKeys?: string[]; - passthroughRequestHeaders?: string[]; - allowedResponseHeaders?: string[]; - identityTokenKey?: string; -}; - -export type EngineOptions = { - version: number; -}; - -export type SecretsEngine = { - path: string; - accessor: string; - config: EngineConfig; - description: string; - externalEntropyAccess: boolean; - local: boolean; - options?: EngineOptions; - pluginVersion: string; - runningPluginVersion: string; - runningSha256: string; - sealWrap: boolean; - type: string; - uuid: string; -}; +import type { MountConfig, MountOptions } from 'vault/mount'; type CommonConfigParams = { rotationPeriod: number; diff --git a/ui/types/vault/services/path-help.d.ts b/ui/types/vault/services/path-help.d.ts new file mode 100644 index 0000000000..6ce020ccc0 --- /dev/null +++ b/ui/types/vault/services/path-help.d.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Service from '@ember/service'; + +import type { PathInfo } from 'vault/utils/openapi-helpers'; + +export default class PathHelpService extends Service { + getPaths(apiPath: string, backend: string, itemType?: string, itemID?: string): Promise; +}