vault/ui/app/components/auth/form-template.ts
Vault Automation 8ce8932117
UI: MFA self-enroll cleanup tasks (#9441) (#9456)
Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
2025-09-19 08:56:44 -07:00

119 lines
4.8 KiB
TypeScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { supportedTypes } from 'vault/utils/auth-form-helpers';
import type VersionService from 'vault/services/version';
import type ClusterModel from 'vault/models/cluster';
import type { UnauthMountsByType } from 'vault/vault/auth/form';
import type { HTMLElementEvent } from 'vault/forms';
/**
* @module Auth::FormTemplate
* This component is responsible for managing the layout and display logic for the auth form. When initialized it fetches
* the unauthenticated sys/internal/ui/mounts endpoint to check the listing_visibility configuration of available mounts.
* If mounts have been configured as listing_visibility="unauth" then tabs render for the corresponding method types,
* otherwise all auth methods display in a dropdown list. The endpoint is re-requested anytime the namespace input is updated.
*
* When auth type changes (by selecting a new one from the dropdown or select a tab), the form component updates and
* dynamically renders the corresponding form.
*
*
* @param {object | null} alternateView - if an alternate view exists, this is the `FormView` (see interface below) data to render that view.
* @param {string} canceledMfaAuth - saved auth type from a cancelled mfa verification
* @param {object} cluster - The route model which is the ember data cluster model. contains information such as cluster id, name and boolean for if the cluster is in standby
* @param {object} defaultView - The `FormView` (see the interface below) data to render the initial view.
* @param {object} initialFormState - sets selectedAuthMethod and showAlternateView based on the login form configuration computed in parent component
* @param {function} handleAuthResponse - callback after the initial authentication request, if an mfaRequirement exists the parent renders the mfa form otherwise it fires the loginAndTransition task in the auth controller
* @param {array} visibleMountTypes - array of auth method types that have mounts with listing_visibility="unauth"
* */
interface Args {
alternateView: FormView | null;
cluster: ClusterModel;
defaultView: FormView;
handleAuthResponse: CallableFunction;
initialFormState: { initialAuthType: string; showAlternate: boolean };
visibleMountTypes: string[];
}
interface FormView {
view: string; // "dropdown" or "tabs"
tabData: UnauthMountsByType | null; // tabs to render if view = "tabs"
}
export default class AuthFormTemplate extends Component<Args> {
@service declare readonly version: VersionService;
supportedAuthTypes: string[];
@tracked errorMessage = '';
@tracked selectedAuthMethod = '';
// true → "Back" button renders, false → "Sign in with other methods→" renders if an alternate view exists
@tracked showAlternateView = false;
constructor(owner: unknown, args: Args) {
super(owner, args);
const { initialAuthType, showAlternate } = this.args.initialFormState;
this.selectedAuthMethod = initialAuthType;
this.showAlternateView = showAlternate;
this.supportedAuthTypes = supportedTypes(this.version.isEnterprise);
}
get tabData() {
if (this.showAlternateView) return this.args?.alternateView?.tabData;
return this.args?.defaultView?.tabData;
}
get formComponent() {
const { selectedAuthMethod } = this;
// isSupported means there is a component file defined for that auth type
const isSupported = this.supportedAuthTypes.includes(selectedAuthMethod);
const formFile = () => (['oidc', 'jwt'].includes(selectedAuthMethod) ? 'oidc-jwt' : selectedAuthMethod);
const component = isSupported ? formFile() : 'base';
// an Auth::Form::<Type> component exists for each method in supported-login-methods
return `auth/form/${component}`;
}
get hideAdvancedSettings() {
// Token does not support custom paths
if (this.selectedAuthMethod === 'token') return true;
// Always show for dropdown mode
if (!this.tabData) return false;
// For remaining scenarios, hide "Advanced settings" if the selected method has visible mount(s)
return this.args.visibleMountTypes?.includes(this.selectedAuthMethod);
}
@action
setAuthType(authType: string) {
this.selectedAuthMethod = authType;
}
@action
setTypeFromDropdown(event: HTMLElementEvent<HTMLInputElement>) {
this.selectedAuthMethod = event.target.value;
}
@action
toggleView() {
this.showAlternateView = !this.showAlternateView;
const firstAuthTab = Object.keys(this.tabData || {})[0];
const type = firstAuthTab || this.args.initialFormState.initialAuthType;
this.setAuthType(type);
}
@action
handleError(message: string) {
this.errorMessage = message;
}
}