From b7880dc3046e6cce7ea212e0881161cae847fad3 Mon Sep 17 00:00:00 2001 From: ewandor Date: Tue, 7 Mar 2023 14:28:21 +0100 Subject: [PATCH] Adding loging logout logic --- front/app/src/app/app.component.html | 3 + front/app/src/app/app.module.ts | 17 ++- .../app/src/app/layout/auth/auth.component.ts | 103 ++++++++++++++++++ .../src/app/layout/auth/auth.interceptor.ts | 34 ++++++ front/app/src/app/layout/auth/auth.service.ts | 63 +++++++++++ 5 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 front/app/src/app/layout/auth/auth.component.ts create mode 100644 front/app/src/app/layout/auth/auth.interceptor.ts create mode 100644 front/app/src/app/layout/auth/auth.service.ts diff --git a/front/app/src/app/app.component.html b/front/app/src/app/app.component.html index 322d6e13..e64b893e 100644 --- a/front/app/src/app/app.component.html +++ b/front/app/src/app/app.component.html @@ -4,6 +4,8 @@
+ +
@@ -13,3 +15,4 @@
+ diff --git a/front/app/src/app/app.module.ts b/front/app/src/app/app.module.ts index b1fae44e..3e5798a1 100644 --- a/front/app/src/app/app.module.ts +++ b/front/app/src/app/app.module.ts @@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'; +import { ReactiveFormsModule } from "@angular/forms"; +import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http"; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -9,20 +11,31 @@ import { AppComponent } from './app.component'; import { SidenavComponent } from "./layout/sidenav/sidenav.component"; import { FlashmessagesComponent } from "./layout/flashmessages/flashmessages.component"; import { FlashmessagesService } from "./layout/flashmessages/flashmessages.service"; +import { LoginComponent, LogoutComponent } from "./layout/auth/auth.component"; +import { AuthService } from "./layout/auth/auth.service"; +import { AuthInterceptor } from "./layout/auth/auth.interceptor" @NgModule({ declarations: [ AppComponent, SidenavComponent, - FlashmessagesComponent + FlashmessagesComponent, + LoginComponent, + LogoutComponent ], imports: [ BrowserModule, AppRoutingModule, NgbModule, NgxBootstrapIconsModule.pick(allIcons), + ReactiveFormsModule, + HttpClientModule, + ], + providers: [ + FlashmessagesService, + AuthService, + { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ], - providers: [FlashmessagesService], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/front/app/src/app/layout/auth/auth.component.ts b/front/app/src/app/layout/auth/auth.component.ts new file mode 100644 index 00000000..05219468 --- /dev/null +++ b/front/app/src/app/layout/auth/auth.component.ts @@ -0,0 +1,103 @@ +import {Component, ElementRef, EventEmitter, Output, ViewChild} from "@angular/core"; +import {FormBuilder, FormGroup, Validators} from "@angular/forms"; +import {Router} from "@angular/router"; +import {AuthService} from "./auth.service"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; + +@Component({ + selector: 'login', + template: ` + + + + + + ` +}) +export class LoginComponent { + @Output() loginSuccess = new EventEmitter() + + @ViewChild('loginModal') + loginModal!: ElementRef; + + form: FormGroup; + + constructor(private fb:FormBuilder, + private authService: AuthService, + private modalService: NgbModal + + ) { + this.form = this.fb.group({ + username: ['',Validators.required], + password: ['',Validators.required] + }); + + this.authService.onAuthenticationRequired$.subscribe((required: boolean) => { + if (required) { + this.openLoginModal() + } + }) + } + + openLoginModal() { + this.modalService.open(this.loginModal, { ariaLabelledBy: 'modal-basic-title' }).result.then( + ); + } + + login() { + const val = this.form.value; + + if (val.username && val.password) { + this.authService.login(val.username, val.password) + .subscribe((answer: string) => { + this.modalService.dismissAll(); + this.loginSuccess.emit(answer) + } + ); + } + } +} + +@Component({ + selector: 'logout', + template: ` + + ` +}) +export class LogoutComponent { + @Output() logoutSuccess = new EventEmitter() + + constructor(private authService: AuthService) { + } + + logout() { + this.authService.logout() + .subscribe(() => { + this.logoutSuccess.emit() + } + ); + } +} diff --git a/front/app/src/app/layout/auth/auth.interceptor.ts b/front/app/src/app/layout/auth/auth.interceptor.ts new file mode 100644 index 00000000..ca358b12 --- /dev/null +++ b/front/app/src/app/layout/auth/auth.interceptor.ts @@ -0,0 +1,34 @@ +import { Injectable } from "@angular/core"; +import { HttpErrorResponse, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http"; + +import { Observable, throwError } from 'rxjs'; +import { catchError } from "rxjs/operators"; + +import { AuthService } from './auth.service'; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + + constructor(private auth: AuthService) {} + + intercept(req: HttpRequest, next: HttpHandler) { + const authToken = this.auth.getAuthorizationToken(); + let authReq: HttpRequest; + if (authToken) { + authReq = req.clone({ + headers: req.headers.set('Authorization', authToken) + }); + } else { + authReq = req; + } + + return next.handle(authReq).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status && error.status == 401) { + this.auth.requestAuth(); + } + return throwError(() => error); + }) + ); + } +} \ No newline at end of file diff --git a/front/app/src/app/layout/auth/auth.service.ts b/front/app/src/app/layout/auth/auth.service.ts new file mode 100644 index 00000000..0946a45d --- /dev/null +++ b/front/app/src/app/layout/auth/auth.service.ts @@ -0,0 +1,63 @@ +import { Injectable } from "@angular/core"; +import { HttpClient, HttpHeaders, HttpParams } from "@angular/common/http"; + +import { map } from "rxjs/operators"; +import { Subject } from "rxjs"; + +import { FlashmessagesService } from "../flashmessages/flashmessages.service"; + +export interface token_answer { + access_token: string, + token_type: string +} + +@Injectable() +export class AuthService { + private access_token: string | null; + + private authenticationRequired = new Subject(); + onAuthenticationRequired$ = this.authenticationRequired.asObservable(); + + constructor(private http: HttpClient, + private flashMessage: FlashmessagesService) { + this.access_token = localStorage.getItem('authtoken') + } + + login(username:string, password:string ) { + const body = new HttpParams() + .set('username', username) + .set('password', password); + + return this.http.post('/api/v1/auth/login', body.toString(), + { + headers: new HttpHeaders() + .set('Content-Type', 'application/x-www-form-urlencoded') + }).pipe(map( v => { + localStorage.setItem('authtoken', v.access_token); + this.access_token = v.access_token + this.flashMessage.success('Login successful. Welcome ' + username); + return username + } )); + } + + logout() { + return this.http.post('/api/v1/auth/logout', '', + { + headers: new HttpHeaders() + }).pipe(map( v => { + localStorage.removeItem('authtoken'); + this.access_token = null; + this.flashMessage.success('Logout successful. Goodbye'); + } )); + } + + getAuthorizationToken() { + return `Bearer ${this.access_token}`; + } + + requestAuth() { + localStorage.removeItem('authtoken'); + this.access_token = null; + this.authenticationRequired.next(true); + } +} \ No newline at end of file