Repository: hashicorp/vault
File: ui/app/components/auth/form/base.ts (TypeScript)

Code before:
    this.login.unlinked().perform(data);
  }

  login = task(
    waitFor(async (formData) => {
      try {
        const authResponse = await this.auth.authenticate({
          clusterId: this.args.cluster.id,
          backend: this.args.authType,
          data: formData,
          selectedAuth: this.args.authType,
        });

        const path = formData?.path;
        this.handleAuthResponse(authResponse, path);
      } catch (error) {
        this.onError(error as Error);
      }
    })
  );

  // Standard methods get mfa_requirements from the authenticate method in the auth service
  // methodData is necessary if there's an MfaRequirement because persisting auth data happens after that
  handleAuthResponse(authResponse: AuthResponse | MfaRequirement, path?: string) {
    const methodData: { selectedAuth: string; path?: string } = { selectedAuth: this.args.authType, path };
    // calls onAuthResponse in parent auth/page.js component
    this.args.onSuccess(authResponse, methodData);
  }

  // SSO methods with a different token exchange workflow skip the auth service authenticate method
  // and need mfa handle separately
  handleMfa(mfaRequirement: MfaRequirement, path: string) {
    const parsedMfaAuthResponse = this.auth._parseMfaResponse(mfaRequirement);
    this.handleAuthResponse(parsedMfaAuthResponse, path);
  }

  onError(error: Error | string) {
    if (!this.auth.mfaErrors) {
      const errorMessage = `Authentication failed: ${this.auth.handleError(error)}`;
      this.args.onError(errorMessage);
    }
  }

  parseFormData(formData: FormData) {
    const data: LoginFields = {};

    // iterate over method specific fields
    for (const field of POSSIBLE_FIELDS) {
      const value = formData.get(field);
      if (value) {
        data[field] = typeof value === 'string' ? value : undefined;

Review comment (security):
Long term plan is that each method component no longer call `this.auth.authenticate` and instead uses the api service for to explicitly make its login request. Once the auth logic isn't so abstracted handling MFA can be consolidated to one place (either here or the auth service) because we will have more control over the authResponse.

Code after:
  onSubmit(event: HTMLElementEvent<HTMLFormElement>) {
    event.preventDefault();
    const formData = new FormData(event.target as HTMLFormElement);
    const data = this.parseFormData(formData);
    this.login.unlinked().perform(data);
  }

  login = task(
    waitFor(async (formData) => {
      try {
        const authResponse = await this.auth.authenticate({
          clusterId: this.args.cluster.id,
          backend: this.args.authType,
          data: formData,
          selectedAuth: this.args.authType,
        });

        const path = formData?.path;
        this.handleAuthResponse(authResponse, path);
      } catch (error) {
        this.onError(error as Error);
      }
    })
  );

  // Standard methods get mfa_requirements from the authenticate method in the auth service
  // methodData is necessary if there's an MfaRequirement because persisting auth data happens after that
  handleAuthResponse(authResponse: AuthResponse | ParsedMfaRequirement, path?: string) {
    const methodData: { selectedAuth: string; path?: string } = { selectedAuth: this.args.authType, path };
    // calls onAuthResponse in parent auth/page.js component
    this.args.onSuccess(authResponse, methodData);
  }

  // SSO methods with a different token exchange workflow skip the auth service authenticate method
  // and need mfa handle separately
  handleMfa(mfaRequirement: MfaRequirementApiResponse, path: string) {
    const parsedMfaAuthResponse = this.auth._parseMfaResponse(mfaRequirement);
    this.handleAuthResponse(parsedMfaAuthResponse, path);
  }

  onError(error: Error | string) {
    if (!this.auth.mfaErrors) {
      const errorMessage = `Authentication failed: ${this.auth.handleError(error)}`;
      this.args.onError(errorMessage);
    }
  }

  parseFormData(formData: FormData) {
    const data: LoginFields = {};

    // iterate over method specific fields