vault/ui/app/models/auth-method.js
claire bontempo 31051ef1e4
UI: Implement api service in auth components (#31085)
* change entity_id to camel casing, remove "backends" key from stored auth data

* fix tokenExpirationEpoch returning NaN, use authSuccess in auth service tests

* camel case mfa_requirement references

* refactor auth service

* implement api service for token method

* implement api service in standard auth methods

* add lookupSelf request to persistAuthData method in auht service instead of calling in components

* implement api service in oidc-jwt component

* implement api service in okta component

* implement api service in saml component

* use api service for wrapped_token query param

* remaining test updates, enterprise tests and stabilize auth helpers

* upate renew() to use new persistAuthData method, add a test

* revert as this will be addressed upstream

* rename supported-login-methods to auth-form-helpers and delete old supported-auth-backends helper, update tests

* cleanup normalize after testing mfa validation for each auth method

* update type declarations, set displayName in each method component

* stabilize redirect tests by waiting for login before asserting url

* stabilize tests

* modernize typescript syntax, move error const to util

* use mirage instead of vault server to resolve test race conditions

* fix file import
2025-07-09 10:11:23 -07:00

170 lines
5.3 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Model, { belongsTo, hasMany, attr } from '@ember-data/model';
import { service } from '@ember/service';
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
import apiPath from 'vault/utils/api-path';
import { withModelValidations } from 'vault/decorators/model-validations';
import lazyCapabilities from 'vault/macros/lazy-capabilities';
import { action } from '@ember/object';
import { camelize } from '@ember/string';
import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
import { supportedTypes } from 'vault/utils/auth-form-helpers';
import engineDisplayData from 'vault/helpers/engines-display-data';
const validations = {
path: [
{ type: 'presence', message: "Path can't be blank." },
{
type: 'containsWhiteSpace',
message: WHITESPACE_WARNING('path'),
level: 'warn',
},
],
};
@withModelValidations(validations)
export default class AuthMethodModel extends Model {
@service namespace;
@service store;
@service version;
@belongsTo('mount-config', { async: false, inverse: null }) config; // one-to-none that replaces former fragment
@hasMany('auth-config', { polymorphic: true, inverse: 'backend', async: false }) authConfigs;
@attr('string') path;
@attr('string') accessor;
@attr('string') name;
@attr('string') type;
// 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") and is set on a getter.
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)}`
: '';
}
@attr('string', {
editType: 'textarea',
})
description;
@attr('boolean', {
helpText:
'When Replication is enabled, a local mount will not be replicated across clusters. This can only be specified at mount time.',
})
local;
@attr('boolean', {
helpText:
'When enabled - if a seal supporting seal wrapping is specified in the configuration, all critical security parameters (CSPs) in this backend will be seal wrapped. (For KV mounts, all values will be seal wrapped.) This can only be specified at mount time.',
})
sealWrap;
// 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);
}
userLockoutConfig = {
modelAttrs: [
'config.lockoutThreshold',
'config.lockoutDuration',
'config.lockoutCounterReset',
'config.lockoutDisable',
],
apiParams: ['lockout_threshold', 'lockout_duration', 'lockout_counter_reset', 'lockout_disable'],
};
get tuneAttrs() {
// order here determines order tune fields render
const tuneAttrs = [
'listingVisibility',
'defaultLeaseTtl',
'maxLeaseTtl',
...(this.methodType === 'token' ? [] : ['tokenType']),
'auditNonHmacRequestKeys',
'auditNonHmacResponseKeys',
'passthroughRequestHeaders',
'allowedResponseHeaders',
'pluginVersion',
...(this.supportsUserLockoutConfig ? this.userLockoutConfig.apiParams.map((a) => camelize(a)) : []),
];
return expandAttributeMeta(this, ['description', `config.{${tuneAttrs.join(',')}}`]);
}
get formFields() {
return [
'type',
'path',
'description',
'accessor',
'local',
'sealWrap',
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion}',
];
}
get formFieldGroups() {
return [
{ default: ['path'] },
{
'Method Options': [
'description',
'config.listingVisibility',
'local',
'sealWrap',
'config.{defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion}',
],
},
];
}
get attrs() {
return expandAttributeMeta(this, this.formFields);
}
get fieldGroups() {
return fieldToAttrs(this, this.formFieldGroups);
}
@lazyCapabilities(apiPath`sys/auth/${'id'}`, 'id') deletePath;
@lazyCapabilities(apiPath`auth/${'id'}/config`, 'id') configPath;
@lazyCapabilities(apiPath`auth/${'id'}/config/client`, 'id') awsConfigPath;
get canDisable() {
return this.deletePath.get('canDelete') !== false;
}
get canEdit() {
return this.configPath.get('canUpdate') !== false;
}
get canEditAws() {
return this.awsConfigPath.get('canUpdate') !== false;
}
@action
tune(data) {
return this.store.adapterFor('auth-method').tune(this.path, data);
}
}