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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | 1x 21x 21x 21x 21x 21x 21x 21x 21x 21x 21x 4x 4x 1x 3x 3x 3x 3x 3x 2x 2x 2x 3x | import { AsyncPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, inject, signal, } from '@angular/core'; import type { Signal, WritableSignal } from '@angular/core'; import { EmailAuthProvider, reauthenticateWithCredential, updatePassword } from '@angular/fire/auth'; import type { User } from '@angular/fire/auth'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; import type { FormControl, ValidationErrors } from '@angular/forms'; import { USER$ } from '@app/core/user.token'; import type { MaybeUser$ } from '@app/core/user.token'; import { SpinnerComponent } from '@app/shared/spinner/spinner.component'; import { AuthErrorMessagesComponent } from '../auth-error-messages/auth-error-messages.component'; import { getErrorCode } from '../error-code'; import { createPasswordControl, PASSWORDS } from '../identity-forms'; import { confirmMatch, confirmMatchFormErrors } from '../validators/confirm-match'; /** * Collects the User's current password and their new password with confirmation. */ type ChangePasswordFormGroup = FormGroup<{ currentPw: FormControl<string | null>; password1: FormControl<string | null>; password2: FormControl<string | null>; }>; /** * Form to change User's password using the current password. */ @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [ AsyncPipe, AuthErrorMessagesComponent, ReactiveFormsModule, SpinnerComponent, ], selector: 'app-change-password', templateUrl: './change-password.component.html', }) export class ChangePasswordComponent { /** Errors specifically for the current password field. */ public readonly $currentPwCntrlErrors: Signal<ValidationErrors | undefined>; /** Aria-invalid attribute for the current password field. */ public readonly $currentPwCntrlInvalid: Signal<boolean>; /** Firebase response error code. */ public readonly $errorCode: WritableSignal<string>; /** Aria-invalid attribute for the form. */ public readonly $formPasswordsInvalid: Signal<boolean>; /** Errors specifically for the first new password field. */ public readonly $password1CntrlErrors: Signal<ValidationErrors | undefined>; /** Aria-invalid attribute for the first new password field. */ public readonly $password1CntrlInvalid: Signal<boolean>; /** Errors specifically for the second new password field. */ public readonly $password2CntrlErrors: Signal<ValidationErrors | undefined>; /** Aria-invalid attribute for the second new password field. */ public readonly $password2CntrlInvalid: Signal<boolean>; /** Toggle showing the form and spinner */ public readonly $showForm: WritableSignal<boolean>; public readonly changePasswordForm: ChangePasswordFormGroup; public readonly currentPwCntrl: FormControl<string | null>; /** Used in error message for password maximum length. */ public readonly maxPasswordLength: number = PASSWORDS.maxLength; /** Used in error message for password minimum length. */ public readonly minPasswordLength: number = PASSWORDS.minLength; public readonly password1Cntrl: FormControl<string | null>; public readonly password2Cntrl: FormControl<string | null>; public readonly user$: MaybeUser$; constructor() { ({ $errors: this.$currentPwCntrlErrors, $invalid: this.$currentPwCntrlInvalid, control: this.currentPwCntrl, } = createPasswordControl()); ({ $errors: this.$password1CntrlErrors, $invalid: this.$password1CntrlInvalid, control: this.password1Cntrl, } = createPasswordControl(true)); ({ $errors: this.$password2CntrlErrors, $invalid: this.$password2CntrlInvalid, control: this.password2Cntrl, } = createPasswordControl()); this.changePasswordForm = new FormGroup( { currentPw: this.currentPwCntrl, password1: this.password1Cntrl, password2: this.password2Cntrl, }, confirmMatch('password1', 'password2'), ); this.$formPasswordsInvalid = confirmMatchFormErrors(this.changePasswordForm, this.password1Cntrl, this.password2Cntrl); this.$errorCode = signal<string>(''); this.$showForm = signal<boolean>(true); // Not handling non-logged in users because the Route guards should. this.user$ = inject(USER$); } /** * Re-authenticates use using the submitted current password, and then updates the password using * the new password from the form. */ public async onSubmit(user: User): Promise<void> { const { currentPw, password1 } = this.changePasswordForm.value; // Validators prevent email1 or password being falsy, but TypeScript doesn't know that. // Additionally, all users are expected to have an email address. if (this.changePasswordForm.invalid || !currentPw || !password1 || !user.email) { throw new Error('Invalid form submitted'); } this.$showForm.set(false); this.$errorCode.set(''); // Clear out any existing errors try { const emailCreds = EmailAuthProvider.credential(user.email, currentPw); const credentials = await reauthenticateWithCredential(user, emailCreds); await updatePassword(credentials.user, password1); } catch (err: unknown) { const code = getErrorCode(err); this.$errorCode.set(code); } this.$showForm.set(true); } } |