Implementation of Better Error Handling on Login
This commit is contained in:
@@ -19,10 +19,16 @@ export class CallbackComponent implements OnInit {
|
||||
|
||||
let auth: 'google' | 'github' = p['auth'];
|
||||
|
||||
if (auth === 'google') {
|
||||
this.loginGoogle(p);
|
||||
} else if (auth === 'github') {
|
||||
this.loginGithub(p);
|
||||
switch (auth) {
|
||||
case "github":
|
||||
this.authService.loginGithubUser(p)
|
||||
break;
|
||||
case "google":
|
||||
this.authService.loginGoogleUser(p)
|
||||
break;
|
||||
default:
|
||||
console.log(`Unimplemented auth: ${auth}`)
|
||||
break;
|
||||
}
|
||||
|
||||
this.router.navigate(['/home'])
|
||||
@@ -30,12 +36,4 @@ export class CallbackComponent implements OnInit {
|
||||
})
|
||||
}
|
||||
|
||||
private loginGoogle(p: any) {
|
||||
this.authService.loginGoogleUser(p)
|
||||
}
|
||||
|
||||
private loginGithub(p: any) {
|
||||
this.authService.loginGithubUser(p)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.error-box {
|
||||
background-color: #ff00001a;
|
||||
min-width: 250px;
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
padding: 25px;
|
||||
margin: 0 auto;
|
||||
color: #ff0000;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="error-box" *ngIf="errorMessage">
|
||||
{{errorMessage}}
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ErrorBoxComponent } from './error-box.component';
|
||||
|
||||
describe('ErrorBoxComponent', () => {
|
||||
let component: ErrorBoxComponent;
|
||||
let fixture: ComponentFixture<ErrorBoxComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ErrorBoxComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ErrorBoxComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
14
src/app/header/header-popup/error-box/error-box.component.ts
Normal file
14
src/app/header/header-popup/error-box/error-box.component.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-error-box',
|
||||
templateUrl: './error-box.component.html',
|
||||
styleUrls: ['./error-box.component.css']
|
||||
})
|
||||
export class ErrorBoxComponent {
|
||||
@Input()
|
||||
errorMessage: string|null = "Error, please try again later."
|
||||
|
||||
constructor() { }
|
||||
|
||||
}
|
||||
@@ -113,12 +113,14 @@
|
||||
transition: .3s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (min-width:767px) {
|
||||
|
||||
.authentication-container {
|
||||
min-width: 630px;
|
||||
}
|
||||
|
||||
|
||||
.authentication-body {
|
||||
all: unset;
|
||||
justify-content: space-around;
|
||||
@@ -145,4 +147,4 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,66 @@
|
||||
<app-popup [state]="popupState"
|
||||
(stateChange)="onStateChange($event)"
|
||||
<app-popup [state]="popupState"
|
||||
(stateChange)="onStateChange($event)"
|
||||
[ignoreClickOutside]="ignoreClickOutside">
|
||||
|
||||
{{errorMessage}}
|
||||
|
||||
<div class="container authentication-container">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onLogin()">
|
||||
<div class="input-div">
|
||||
<fa-icon class="input-div-icon"
|
||||
[icon]="_userIcon">
|
||||
</fa-icon>
|
||||
<input type="text" id="username"
|
||||
formControlName="username"
|
||||
class="form-control"
|
||||
placeholder="Username">
|
||||
</div>
|
||||
<div class="input-div">
|
||||
<fa-icon class="input-div-icon"
|
||||
[icon]="_passwordIcon">
|
||||
</fa-icon>
|
||||
<input type="password" id="password"
|
||||
formControlName="password"
|
||||
class="form-control"
|
||||
placeholder="Password">
|
||||
</div>
|
||||
<button class="btn"
|
||||
type="submit">
|
||||
Login
|
||||
<div class="container overflow-hidden"
|
||||
[@resizeContainerForErrorMessage]="hideErrorMessage()">
|
||||
|
||||
<app-error-box [errorMessage]="errorMessage"
|
||||
[@showErrorMessage]="showErrorMessage()">
|
||||
</app-error-box>
|
||||
|
||||
<div class="container authentication-container"
|
||||
[@hideAuthContainer]="hideErrorMessage()"
|
||||
(@hideAuthContainer.done)="hideAuthContainer($event)">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onLogin()">
|
||||
<div class="input-div">
|
||||
<fa-icon class="input-div-icon"
|
||||
[icon]="_userIcon">
|
||||
</fa-icon>
|
||||
<input type="text" id="username"
|
||||
formControlName="username"
|
||||
class="form-control"
|
||||
placeholder="Username">
|
||||
</div>
|
||||
<div class="input-div">
|
||||
<fa-icon class="input-div-icon"
|
||||
[icon]="_passwordIcon">
|
||||
</fa-icon>
|
||||
<input type="password" id="password"
|
||||
formControlName="password"
|
||||
class="form-control"
|
||||
placeholder="Password">
|
||||
</div>
|
||||
<button class="btn"
|
||||
type="submit">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="separator-line">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<button mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
(click)="onGoogleLogin()">
|
||||
<mat-icon style="width: 50px; height:30px"
|
||||
svgIcon="google-logo"></mat-icon>
|
||||
Login With Google
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="separator-line">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="col-lg-6 authentication-body">
|
||||
<button mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
(click)="onGoogleLogin()">
|
||||
<mat-icon style="width: 50px; height:30px"
|
||||
svgIcon="google-logo"></mat-icon>
|
||||
Login With Google
|
||||
</button>
|
||||
<button mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
(click)="onGithubLogin()">
|
||||
<mat-icon style="width: 50px; height:30px"
|
||||
svgIcon="github-logo"></mat-icon>
|
||||
Login With Github
|
||||
</button>
|
||||
<button mat-button
|
||||
class="oauth-button d-flex justify-content-center align-items-center"
|
||||
(click)="onGithubLogin()">
|
||||
<mat-icon style="width: 50px; height:30px"
|
||||
svgIcon="github-logo"></mat-icon>
|
||||
Login With Github
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</app-popup>
|
||||
</app-popup>
|
||||
|
||||
@@ -3,12 +3,13 @@ import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { faLock, faUser } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {Subscription, timeout} from 'rxjs';
|
||||
import { AuthService } from 'src/app/shared/auth/auth.service';
|
||||
import { HttpError } from 'src/app/shared/model/httpError/httpError.model';
|
||||
import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker';
|
||||
import UserChecker from 'src/app/shared/model/user/user.checker';
|
||||
import { User } from 'src/app/shared/model/user/user.model';
|
||||
import {animate, animateChild, group, query, state, style, transition, trigger} from "@angular/animations";
|
||||
|
||||
|
||||
const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg";
|
||||
@@ -17,18 +18,81 @@ const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.css']
|
||||
styleUrls: ['./login.component.css'],
|
||||
animations: [
|
||||
trigger('resizeContainerForErrorMessage', [
|
||||
state('hide',
|
||||
style({
|
||||
height: '100px',
|
||||
width: '370px',
|
||||
})
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query(
|
||||
"@*",
|
||||
animateChild(),
|
||||
{ optional: true }
|
||||
),
|
||||
animate('1s ease')
|
||||
])
|
||||
)
|
||||
]),
|
||||
trigger('showErrorMessage', [
|
||||
state('show',
|
||||
style({
|
||||
opacity: 1,
|
||||
height: '100px',
|
||||
width: '340px',
|
||||
})
|
||||
),
|
||||
state('hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
height: '0px',
|
||||
width: '0px',
|
||||
})
|
||||
),
|
||||
transition(
|
||||
'* => show',
|
||||
animate(
|
||||
'500ms ease-in'
|
||||
)
|
||||
),
|
||||
]),
|
||||
trigger('hideAuthContainer', [
|
||||
state('hide',
|
||||
style({
|
||||
opacity: 0,
|
||||
})
|
||||
),
|
||||
transition(
|
||||
'show => hide',
|
||||
group([
|
||||
query(
|
||||
"@*",
|
||||
animateChild(),
|
||||
{ optional: true }
|
||||
),
|
||||
animate(
|
||||
'250ms ease-out'
|
||||
)
|
||||
])
|
||||
)
|
||||
]),
|
||||
]
|
||||
})
|
||||
export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
@Input()
|
||||
state: boolean = false;
|
||||
state: boolean = false;
|
||||
|
||||
@Input()
|
||||
ignoreClickOutside!: HTMLDivElement[];
|
||||
ignoreClickOutside!: HTMLDivElement[];
|
||||
|
||||
@Output()
|
||||
stateChange = new EventEmitter<boolean>();
|
||||
stateChange = new EventEmitter<boolean>();
|
||||
|
||||
popupState = false;
|
||||
|
||||
@@ -38,6 +102,8 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
errorMessage!: string | null;
|
||||
|
||||
isShowErrorMessage = false;
|
||||
|
||||
_userIcon = faUser;
|
||||
|
||||
_passwordIcon = faLock;
|
||||
@@ -47,14 +113,14 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private matIconRegistry: MatIconRegistry,
|
||||
private domSanitizer: DomSanitizer) {
|
||||
this.matIconRegistry.addSvgIcon(
|
||||
"google-logo",
|
||||
this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG)
|
||||
);
|
||||
this.matIconRegistry.addSvgIcon(
|
||||
"github-logo",
|
||||
this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG)
|
||||
);
|
||||
this.matIconRegistry.addSvgIcon(
|
||||
"google-logo",
|
||||
this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG)
|
||||
);
|
||||
this.matIconRegistry.addSvgIcon(
|
||||
"github-logo",
|
||||
this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -89,7 +155,6 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
password: this.loginForm.controls['password'].value
|
||||
}
|
||||
this.authService.login(user);
|
||||
|
||||
}
|
||||
|
||||
onGoogleLogin() {
|
||||
@@ -114,4 +179,25 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.loginForm.reset();
|
||||
}
|
||||
|
||||
public showErrorMessage(): string {
|
||||
if (this.isShowErrorMessage) {
|
||||
return "show";
|
||||
}
|
||||
return "hide";
|
||||
}
|
||||
|
||||
public hideErrorMessage(): string {
|
||||
if (!!this.errorMessage) {
|
||||
return "hide";
|
||||
}
|
||||
return "show";
|
||||
}
|
||||
|
||||
hideAuthContainer(event: any) {
|
||||
if (event.toState === "hide") {
|
||||
event.element.style.display = "none";
|
||||
this.isShowErrorMessage = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||
|
||||
import { HeaderComponent } from './header.component';
|
||||
@@ -15,6 +15,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { SignupComponent } from './header-popup/signup/signup.component';
|
||||
import { CallbackComponent } from './header-popup/callback/callback.component';
|
||||
import {MatIconModule} from '@angular/material/icon';
|
||||
import { ErrorBoxComponent } from './header-popup/error-box/error-box.component';
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +28,8 @@ import {MatIconModule} from '@angular/material/icon';
|
||||
HeaderDropdownComponent,
|
||||
LoginComponent,
|
||||
SignupComponent,
|
||||
CallbackComponent
|
||||
CallbackComponent,
|
||||
ErrorBoxComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -37,7 +39,8 @@ import {MatIconModule} from '@angular/material/icon';
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatIconModule,
|
||||
SharedModule
|
||||
SharedModule,
|
||||
NgOptimizedImage
|
||||
], exports: [
|
||||
HeaderComponent,
|
||||
HeaderSliderComponent,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { first, firstValueFrom, Observable, Subject, take } from 'rxjs';
|
||||
import {first, firstValueFrom, Observable, of, Subject, take} from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { param } from 'ts-interface-checker';
|
||||
import { HttpError } from '../model/httpError/httpError.model';
|
||||
import { Token } from '../model/token/token.model';
|
||||
import { User } from '../model/user/user.model';
|
||||
@@ -25,11 +25,11 @@ export class AuthService {
|
||||
login(userAuthAtempt: User): void {
|
||||
this.validateUser(this.loginUser(userAuthAtempt));
|
||||
}
|
||||
|
||||
|
||||
googleLogin() {
|
||||
window.open(this.BACKEND_OAUTH_PATH + '/oauth2/authorization/google', '_self');
|
||||
}
|
||||
|
||||
|
||||
githubLogin() {
|
||||
window.open(this.BACKEND_OAUTH_PATH + '/oauth2/authorization/github', '_self');
|
||||
}
|
||||
@@ -95,9 +95,9 @@ export class AuthService {
|
||||
|
||||
return this.http.get<User>(
|
||||
this.BACKEND_OAUTH_PATH + '/login/oauth2/code/google',
|
||||
{
|
||||
{
|
||||
withCredentials: true,
|
||||
params: params
|
||||
params: params
|
||||
},
|
||||
).pipe(
|
||||
first()
|
||||
@@ -114,9 +114,9 @@ export class AuthService {
|
||||
|
||||
return this.http.get<User>(
|
||||
this.BACKEND_OAUTH_PATH + '/login/oauth2/code/github',
|
||||
{
|
||||
{
|
||||
withCredentials: true,
|
||||
params: params
|
||||
params: params
|
||||
},
|
||||
).pipe(
|
||||
first()
|
||||
@@ -133,18 +133,6 @@ export class AuthService {
|
||||
)
|
||||
}
|
||||
|
||||
private validateUser(userAuthAtempt: Observable<User>) {
|
||||
userAuthAtempt.subscribe({
|
||||
next: userAuthentication => {
|
||||
this.userAuthenticated = <User>userAuthentication;
|
||||
this.authSubject.next(this.userAuthenticated);
|
||||
},
|
||||
error: err => {
|
||||
this.authSubject.next(<HttpError>err.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private refreshAccessToken() {
|
||||
return firstValueFrom(this.http.post(
|
||||
this.BACKEND_PATH + "/user/login/refresh",
|
||||
@@ -157,19 +145,37 @@ export class AuthService {
|
||||
return this.http.get<User>(
|
||||
this.BACKEND_PATH + '/session/validate',
|
||||
{ withCredentials: true }
|
||||
).pipe(
|
||||
first()
|
||||
);
|
||||
}
|
||||
|
||||
private destroySessions() {
|
||||
return this.http.post(
|
||||
return this.http.delete(
|
||||
this.BACKEND_PATH + '/session/destroy',
|
||||
{},
|
||||
{ withCredentials: true }
|
||||
).pipe(
|
||||
take(1)
|
||||
);
|
||||
}
|
||||
|
||||
private validateUser(userAuthAtempt: Observable<User>) {
|
||||
userAuthAtempt.pipe(
|
||||
catchError(error => {
|
||||
if (error.status == 0) {
|
||||
return of(<HttpError>{
|
||||
title: "Service Unavailable",
|
||||
status: 500,
|
||||
details: "Service Unavailable, please try again later.",
|
||||
developerMessage: "Service Unavailable, please try again later.",
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
return of(<HttpError>error.error);
|
||||
}),
|
||||
first()
|
||||
).subscribe({
|
||||
next: userAuthentication => {
|
||||
this.userAuthenticated = <User>userAuthentication;
|
||||
this.authSubject.next(this.userAuthenticated);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
.popup-body {
|
||||
height: calc(100% - 120px);
|
||||
padding: 0px 60px;
|
||||
padding: 0px 30px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -80,4 +80,4 @@
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation }
|
||||
'* => show',
|
||||
group([
|
||||
query(
|
||||
"@*",
|
||||
"@*",
|
||||
animateChild(),
|
||||
{ optional: true }
|
||||
),
|
||||
@@ -28,7 +28,7 @@ import { Component, ElementRef, EventEmitter, Input, Output, ViewEncapsulation }
|
||||
'show => hide',
|
||||
group([
|
||||
query(
|
||||
"@*",
|
||||
"@*",
|
||||
animateChild(),
|
||||
{ optional: true }
|
||||
),
|
||||
@@ -51,7 +51,7 @@ export class PopupComponent {
|
||||
|
||||
constructor() { }
|
||||
|
||||
get popupState(): string {
|
||||
get popupState(): string {
|
||||
return this.state ? 'show' : 'hide';
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import { PopupComponent } from './components/popup/popup.component';
|
||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
declarations: [
|
||||
ClickedOutsideDirective,
|
||||
SliderItemComponent,
|
||||
PopupComponent
|
||||
PopupComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
Reference in New Issue
Block a user