All files / app/identity/forgot-password forgot-password.component.ts

100% Statements 16/16
100% Branches 3/3
100% Functions 2/2
100% Lines 16/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                                                      1x                             10x   10x   10x   10x 10x                   3x     3x 1x     2x 2x   2x 2x   1x 1x     2x      
import {
  ChangeDetectionStrategy,
  Component,
  inject,
  signal,
} from '@angular/core';
import type { Signal, WritableSignal } from '@angular/core';
import { Auth, sendPasswordResetEmail } from '@angular/fire/auth';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import type { FormControl, ValidationErrors } from '@angular/forms';
import { RouterLink } from '@angular/router';
 
import { SpinnerComponent } from '@app/shared/spinner/spinner.component';
 
import { AuthErrorMessagesComponent } from '../auth-error-messages/auth-error-messages.component';
import { getErrorCode } from '../error-code';
import { createEmailControl } from '../identity-forms';
 
/** FormGroup allows the use of (ngSubmit) on the Form element. */
type ForgotFormGroup = FormGroup<{ email: FormControl<string | null> }>;
 
/**
 * Sends email to reset password for a user's account.
 */
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    AuthErrorMessagesComponent,
    ReactiveFormsModule,
    RouterLink,
    SpinnerComponent,
  ],
  selector: 'app-forgot-password',
  templateUrl: './forgot-password.component.html',
})
export class ForgotPasswordComponent {
  /** Errors specific to the email field. */
  public readonly $emailCntrlErrors: Signal<ValidationErrors | undefined>;
  /** Aria-invalid attribute for the email field. */
  public readonly $emailCntrlInvalid: Signal<boolean>;
  /** Form submission errors from Firebase Authentication response. */
  public readonly $errorCode: WritableSignal<string>;
  /** Toggle the HTML form and spinner. */
  public readonly $showForm: WritableSignal<boolean>;
  public readonly emailCntrl: FormControl<string | null>;
  public readonly forgotForm: ForgotFormGroup;
 
  private readonly _auth: Auth;
 
  constructor() {
    this._auth = inject(Auth);
 
    ({ $errors: this.$emailCntrlErrors, $invalid: this.$emailCntrlInvalid, control: this.emailCntrl } = createEmailControl());
 
    this.forgotForm = new FormGroup({ email: this.emailCntrl });
 
    this.$errorCode = signal<string>('');
    this.$showForm = signal<boolean>(true);
  }
 
  /**
   * Sends password reset email to submitted email address, if it exists in Firebase Authentication.
   *
   * Email enumeration protection is enabled in production, so errors should not indicate if an
   * account exists with the submitted email address.
   */
  public async onSubmit(): Promise<void> {
    const { email } = this.forgotForm.value;
 
    // Validators prevent email being falsy, but TypeScript doesn't know that.
    if (this.forgotForm.invalid || !email) {
      throw new Error('Invalid form submitted');
    }
 
    this.$showForm.set(false);
    this.$errorCode.set(''); // Clear out any existing errors
 
    try {
      await sendPasswordResetEmail(this._auth, email);
    } catch (err: unknown) {
      const code = getErrorCode(err);
      this.$errorCode.set(code);
    }
 
    this.$showForm.set(true);
  }
}