/** * 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 type { AuthResponse, AuthResponseWithMfa } from 'vault/vault/services/auth'; import type { UnauthMountsByType, UnauthMountsResponse } from 'vault/vault/auth/form'; import type ClusterModel from 'vault/models/cluster'; import type CspEventService from 'vault/services/csp-event'; /** * @module AuthPage * The Auth::Page is the route template for the login splash view. It renders the Auth::FormTemplate or MFA component if an * mfa validation is returned from the auth request. It also formats mount data to manage what tabs are rendered in Auth::FormTemplate. * * @example * * * @param {object} cluster - the ember data cluster model. contains information such as cluster id, name and boolean for if the cluster is in standby * @param {object} directLinkData - mount data built from the "with" query param. If param is a mount path and maps to a visible mount, the login form defaults to this mount. Otherwise the form preselects the passed auth type. * @param {object} loginSettings - * enterprise only * login settings configured for the namespace * @param {string} namespaceQueryParam - namespace to login with, updated by typing in to the namespace input * @param {string} oidcProviderQueryParam - oidc provider query param, set in url as "?o=someprovider" * @param {function} onAuthSuccess - callback task in controller that receives the auth response (after MFA, if enabled) when login is successful * @param {function} onNamespaceUpdate - callback task that passes user input to the controller to update the login namespace in the url query params * @param {object} visibleAuthMounts - response from unauthenticated request to sys/internal/ui/mounts which returns mount paths tuned with `listing_visibility="unauth"`. keys are the mount path, values are mount data such as "type" or "description," if it exists * */ export const CSP_ERROR = "This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI."; interface Args { visibleAuthMounts: UnauthMountsResponse; cluster: ClusterModel; onAuthSuccess: CallableFunction; } interface MfaAuthData { mfa_requirement: object; selectedAuth: string; path: string; } export default class AuthPage extends Component { @service('csp-event') declare readonly csp: CspEventService; @tracked canceledMfaAuth = ''; @tracked mfaAuthData: MfaAuthData | null = null; @tracked mfaErrors = ''; get visibleMountsByType() { const visibleAuthMounts = this.args.visibleAuthMounts; if (visibleAuthMounts) { const authMounts = visibleAuthMounts; return Object.entries(authMounts).reduce((obj, [path, mountData]) => { const { type } = mountData; obj[type] ??= []; // if an array doesn't already exist for that type, create it obj[type].push({ path, ...mountData }); return obj; }, {} as UnauthMountsByType); } return null; } get cspError() { const isStandby = this.args.cluster.standby; const hasConnectionViolations = this.csp.connectionViolations.length; return isStandby && hasConnectionViolations ? CSP_ERROR : ''; } @action onAuthResponse(authResponse: AuthResponse | AuthResponseWithMfa, { selectedAuth = '', path = '' }) { const mfa_requirement = 'mfa_requirement' in authResponse ? authResponse.mfa_requirement : undefined; /* Checking for an mfa_requirement happens in two places. If doSubmit in is called directly (by the
component) mfa is just handled here. Login methods submitted using a child form component of are first checked for mfa in the Auth::LoginForm "authenticate" task, and then that data eventually bubbles up here. */ if (mfa_requirement) { // if an mfa requirement exists further action is required this.mfaAuthData = { mfa_requirement, selectedAuth, path }; } else { // calls authSuccess in auth.js controller this.args.onAuthSuccess(authResponse); } } @action onCancelMfa() { // before resetting mfaAuthData, preserve auth type this.canceledMfaAuth = this.mfaAuthData?.selectedAuth ?? ''; this.mfaAuthData = null; } @action onMfaSuccess(authResponse: AuthResponse) { // calls authSuccess in auth.js controller this.args.onAuthSuccess(authResponse); } @action onMfaErrorDismiss() { this.mfaAuthData = null; this.mfaErrors = ''; } }