import { HttpErrorResponse } from '@angular/common/http'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { ApplicantService } from '@core/services/auth-user/applicant.service'; import { DeepLinkingService } from '@core/services/deep-linking.service'; import { PortalDeterminationService } from '@core/services/portal-determination.service'; import { SpinnerService } from '@core/services/spinner.service'; import { SSOService } from '@core/services/sso.service'; import { AuthBehaviors } from '@core/services/token/token-behaviors'; import { TokenService } from '@core/services/token/token.service'; import { AuthState } from '@core/states/auth.state'; import { LoginBehaviors } from '@core/typings/login-behaviors.typing'; import { ClientUserService } from '@features/client-user/client-user.service'; import { EmailExtensionValidator, PanelTypes, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { CACHE_TYPES, getStorage } from '@yourcause/common/cache'; import { I18nService } from '@yourcause/common/i18n'; import { LogService } from '@yourcause/common/logging'; import { NotifierService } from '@yourcause/common/notifier'; import { AppInsightsService } from '@yourcause/common/utils'; export const REMEMBER_ME = 'remember-me'; const storage = getStorage(CACHE_TYPES.LOCALSTORAGE); interface LoginGroup { username: string; password: string; rememberMe: boolean; } @Component({ selector: 'gc-login-form', templateUrl: './login-form.component.html', styleUrls: ['./login-form.component.scss'] }) export class LoginFormComponent implements OnInit, OnDestroy { PanelTypes = PanelTypes; formGroup: TypeSafeFormGroup; rememberMe: boolean; username = ''; password = ''; passwordVisible: boolean; ssoLoginUrl: string; loggingInWithSSO = false; passwordSecurityResetMessage = ''; accountIsLocked = false; passwordExpired = false; sub = this.ssoService.changesTo$('ssoConfig') .subscribe(ssoConfig => { if (ssoConfig && ssoConfig.loginBehavior !== LoginBehaviors.PlainLogin) { const prefix = this.portal.getCurrentPrefix(); this.ssoLoginUrl = this.ssoService.generateUrl(ssoConfig.affiliateId, prefix); if (ssoConfig.loginBehavior === LoginBehaviors.SSO) { this.loginWithSSO(); } } }); constructor ( private logger: LogService, private appInsights: AppInsightsService, private behavior: AuthBehaviors, private tokenService: TokenService, private applicantService: ApplicantService, private formBuilder: TypeSafeFormBuilder, private router: Router, private clientUserService: ClientUserService, private notifier: NotifierService, private i18nService: I18nService, private deepLinkingService: DeepLinkingService, private authState: AuthState, private spinnerService: SpinnerService, private portal: PortalDeterminationService, private ssoService: SSOService ) { } get isManager () { return this.portal.isManager; } get isPlatform () { return this.portal.isPlatform; } get passwordService () { return this.isManager ? this.clientUserService : this.applicantService; } async ngOnInit () { const cache = storage.getItem(REMEMBER_ME); this.rememberMe = !!cache; this.username = this.authState.username; if (this.rememberMe && !this.username) { this.username = cache; } this.formGroup = this.formBuilder.group({ username: [this.username, [Validators.required, EmailExtensionValidator]], password: [this.password, Validators.required], rememberMe: [this.rememberMe] }); this._onSubmit = this._onSubmit.bind(this); } loginWithSSO () { const prefix = this.portal.getCurrentPrefix(); if ( prefix === 'barings' || prefix === 'qa-chs' ) { this.appInsights.trackEvent('sso:debug', { event: 'LOGIN_TRIGGERED', prefix }); } this.spinnerService.startSpinner(); this.loggingInWithSSO = true; setTimeout(() => { location.href = this.ssoLoginUrl; }, 50); } getRouteSlice () { let routeSlice = 'apply'; if (this.isManager) { routeSlice = 'management'; } return routeSlice; } goToForgotPassword () { this.router.navigate([`/${this.getRouteSlice()}/auth/forgot`]); } goToVerificationCodePage () { this.router.navigate([`/${this.getRouteSlice()}/auth/verification-code`]); } goToResendVerification () { this.router.navigate([`/${this.getRouteSlice()}/auth/resend-verification`]); } goToResetPassword (isRootUser = false) { const route = this.portal.getRoute( 'auth/reset-password' ); this.router.navigate([route], { queryParams: { isRootUser } }); } async resendPasswordEmail () { this.spinnerService.startSpinner(); const email = (this.formGroup.get('username').value || '').trim(); await this.passwordService.requestPasswordEmail(email); this.notifier.success((this.i18nService.translate( 'AUTH:textSuccessResentPasswordResetEmail', {}, 'Successfully resent the password reset email' ))); this.spinnerService.stopSpinner(); } togglePasswordVisible = () => { this.passwordVisible = !this.passwordVisible; }; setUsername (username: string) { this.authState.set('username', username); } setPassword (password: string) { this.authState.set('password', password); } async _onSubmit () { this.spinnerService.startSpinner(); this.accountIsLocked = false; const email = (this.formGroup.get('username').value || '').trim(); const password = (this.formGroup.get('password').value || '').trim(); this.setUsername(email); this.setPassword(password); this.rememberMe = this.formGroup.get('rememberMe').value; if (this.rememberMe) { storage.setItem(REMEMBER_ME, email); } else { storage.removeItem(REMEMBER_ME); } try { window.scrollTo(0, 0); await this.tokenService.login(email, password); } catch (e) { return this.handleError(e as HttpErrorResponse); } let proceed = false; if (this.portal.isManager) { proceed = await this.handleManagerLogin(); } else if (this.portal.isApply) { proceed = await this.handleApplicantLogin(); } if (proceed) { this.deepLinkingService.attemptDeepLink(this.behavior.current.postLoginRedirect); } this.spinnerService.stopSpinner(); } async handleManagerLogin () { const user = await this.clientUserService.getUser(true, true); if (user && user.isRootUser && user.requirePasswordReset) { this.goToResetPassword(true); return false; } return true; } async handleApplicantLogin () { await this.applicantService.getApplicant(true); return true; } async handleError (e: HttpErrorResponse) { this.logger.error(e); switch (e?.error?.message) { case 'New password required for hashing implementation.': { await this.passwordService.requestPasswordEmail( this.formGroup.value.username ); this.passwordSecurityResetMessage = this.i18nService.translate( 'AUTH:texPasswordSecurityResetMessage', {}, `We've upgraded our password security and it appears that this is the first time you are accessing the system since then. An email has been sent to you with a link that will allow you to reset your password.` ); break; } case 'Incorrect email or password': { this.notifier.error(this.i18nService.translate( 'AUTH.incorrectPasswordOrEmail', {}, 'Incorrect email or password' )); break; } case 'Account is locked': { this.accountIsLocked = true; this.notifier.error(this.i18nService.translate( 'AUTH:textAccountHasBeenLocked`', {}, 'Your account has been locked because of too many failed attempts to login' )); break; } case 'Account not active': { this.notifier.error(this.i18nService.translate( 'AUTH.accountNotActive', {}, 'Your account is not active.' )); break; } case 'Email not confirmed': { this.notifier.error(this.i18nService.translate( 'AUTH.accountNotConfirmed', {}, 'Email not confirmed. Click the link below to resend a verification email' )); break; } case 'Security_Code_Required': { this.goToVerificationCodePage(); break; } case 'The password is expired.': { const email = (this.formGroup.get('username').value || '').trim(); await this.passwordService.requestPasswordEmail(email); this.passwordExpired = true; break; } default: { this.appInsights.trackException(e, null, { wasAngular: 'True' }); this.notifier.error(this.i18nService.translate( 'AUTH.errorLoggingIn', {}, 'There was an error logging in' )); } } this.spinnerService.stopSpinner(); } ngOnDestroy () { this.sub.unsubscribe(); } }