/** * 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 { @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:: 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) { 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; } }