From ba881aefa8d91ac96dccfc595a71be2a94046db4 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 16 Oct 2022 12:06:18 +0200 Subject: [PATCH] Added verify-login #70 --- .../src/bot_api/controller/auth_controller.py | 16 +++- .../exception/service_error_code_enum.py | 4 + kdb-bot/src/bot_api/route/route.py | 17 +++-- .../models/error/service-error-code.enum.ts | 3 +- .../auth-user/auth-user.component.ts | 2 +- kdb-web/src/app/services/auth/auth.service.ts | 75 ++++++++++--------- .../error-handler/error-handler.service.ts | 13 +++- .../app/services/settings/settings.service.ts | 2 +- .../src/app/services/theme/theme.service.ts | 8 +- 9 files changed, 93 insertions(+), 47 deletions(-) diff --git a/kdb-bot/src/bot_api/controller/auth_controller.py b/kdb-bot/src/bot_api/controller/auth_controller.py index ee64bf656b..5342df92c6 100644 --- a/kdb-bot/src/bot_api/controller/auth_controller.py +++ b/kdb-bot/src/bot_api/controller/auth_controller.py @@ -6,6 +6,8 @@ from flask import request, jsonify, Response from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.api import Api +from bot_api.exception.service_error_code_enum import ServiceErrorCode +from bot_api.exception.service_exception import ServiceException from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria from bot_api.json_processor import JSONProcessor from bot_api.logging.api_logger import ApiLogger @@ -76,6 +78,19 @@ class AuthController: result = await self._auth_service.login_async(dto) return jsonify(result.to_dict()) + @Route.get(f'{BasePath}/verify-login') + async def verify_login(self): + token = None + result = False + if 'Authorization' in request.headers: + bearer = request.headers.get('Authorization') + token = bearer.split()[1] + + if token is not None: + result = self._auth_service.verify_login(token) + + return jsonify(result) + @Route.post(f'{BasePath}/forgot-password/') async def forgot_password(self, email: str): await self._auth_service.forgot_password_async(email) @@ -108,7 +123,6 @@ class AuthController: return jsonify(result.to_dict()) @Route.post(f'{BasePath}/revoke') - @Route.authorize async def revoke(self): dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) await self._auth_service.revoke_async(dto) diff --git a/kdb-bot/src/bot_api/exception/service_error_code_enum.py b/kdb-bot/src/bot_api/exception/service_error_code_enum.py index 24da48ed24..7b19aeb18c 100644 --- a/kdb-bot/src/bot_api/exception/service_error_code_enum.py +++ b/kdb-bot/src/bot_api/exception/service_error_code_enum.py @@ -1,5 +1,7 @@ from enum import Enum +from werkzeug.exceptions import Unauthorized + class ServiceErrorCode(Enum): @@ -17,3 +19,5 @@ class ServiceErrorCode(Enum): ConnectionFailed = 8 Timeout = 9 MailError = 10 + + Unauthorized = 11 diff --git a/kdb-bot/src/bot_api/route/route.py b/kdb-bot/src/bot_api/route/route.py index 429b24a367..ce8826bcd1 100644 --- a/kdb-bot/src/bot_api/route/route.py +++ b/kdb-bot/src/bot_api/route/route.py @@ -1,12 +1,13 @@ from functools import wraps from typing import Optional -from flask import request +from flask import request, jsonify from flask_cors import cross_origin from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_exception import ServiceException +from bot_api.model.error_dto import ErrorDTO from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC @@ -30,14 +31,20 @@ class Route: bearer = request.headers.get('Authorization') token = bearer.split()[1] - if not token: - raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set') + if token is None: + ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token not set') + error = ErrorDTO(ex.error_code, ex.message) + return jsonify(error.to_dict()), 401 if cls._auth_users is None or cls._auth is None: - raise ServiceException(ServiceErrorCode.InvalidDependencies, f'Authorize is not initialized') + ex = ServiceException(ServiceErrorCode.Unauthorized, f'Authorize is not initialized') + error = ErrorDTO(ex.error_code, ex.message) + return jsonify(error.to_dict()), 401 if not cls._auth.verify_login(token): - raise ServiceException(ServiceErrorCode.InvalidUser, f'Token expired') + ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token expired') + error = ErrorDTO(ex.error_code, ex.message) + return jsonify(error.to_dict()), 401 return await f(*args, **kwargs) diff --git a/kdb-web/src/app/models/error/service-error-code.enum.ts b/kdb-web/src/app/models/error/service-error-code.enum.ts index 5853b5d8fe..5e32aaf18b 100644 --- a/kdb-web/src/app/models/error/service-error-code.enum.ts +++ b/kdb-web/src/app/models/error/service-error-code.enum.ts @@ -12,5 +12,6 @@ export enum ServiceErrorCode { ConnectionFailed = 8, Timeout = 9, - MailError = 10 + MailError = 10, + Unauthorized = 11 } diff --git a/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts b/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts index 598b18d4fe..c972e7f215 100644 --- a/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts +++ b/kdb-web/src/app/modules/admin/auth-users/components/auth-user/auth-user.component.ts @@ -136,7 +136,7 @@ export class AuthUserComponent implements OnInit { loadNextPage() { this.authService.getFilteredUsers(this.searchCriterions).pipe(catchError(err => { this.loading = false; - return throwError(err); + return throwError(() => err); })).subscribe(list => { this.totalRecords = list.totalCount; this.users = list.users; diff --git a/kdb-web/src/app/services/auth/auth.service.ts b/kdb-web/src/app/services/auth/auth.service.ts index f388e291f1..1608c3f485 100644 --- a/kdb-web/src/app/services/auth/auth.service.ts +++ b/kdb-web/src/app/services/auth/auth.service.ts @@ -2,7 +2,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { JwtHelperService } from '@auth0/angular-jwt'; -import { Observable, Subscription } from 'rxjs'; +import { firstValueFrom, Observable, Subscription } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { AdminUpdateUserDTO } from 'src/app/models/auth/admin-update-user.dto'; import { AuthRoles } from 'src/app/models/auth/auth-roles.enum'; @@ -91,6 +91,14 @@ export class AuthService { }); } + verifyLogin(): Observable { + return this.http.get(`${this.appsettings.getApiURL()}/api/auth/verify-login`, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }); + } + forgotPassword(email: string): Observable { const emailJson = JSON.stringify(email); return this.http.post(`${this.appsettings.getApiURL()}/api/auth/forgot-password`, emailJson, { @@ -141,27 +149,6 @@ export class AuthService { }); } - logout(): Subscription | null { - const token = this.getToken(); - this.isLoggedIn = false; - localStorage.removeItem('jwt'); - localStorage.removeItem('rjwt'); - this.router.navigate(['/auth/login']); - - if (token && token.token && token.refreshToken) { - return this.http.post(`${this.appsettings.getApiURL()}/api/auth/revoke`, token, { - headers: new HttpHeaders({ - 'Content-Type': 'application/json' - }) - }).pipe(catchError((error: any) => { - error.error = null; - throw error; - })).subscribe(); - } - - return null - } - deleteUserByMail(mail: string) { return this.http.post(`${this.appsettings.getApiURL()}/api/auth/delete-user-by-mail/${mail}`, { headers: new HttpHeaders({ @@ -190,39 +177,55 @@ export class AuthService { return this.jwtHelper.decodeToken(this.getToken().token); } - async isUserLoggedInAsync(): Promise { - this.isLoggedIn = await this._isUserLoggedInAsync(); - return this.isLoggedIn; + logout(): Subscription | null { + const token = this.getToken(); + + if (token && token.token && token.refreshToken) { + return this.http.post(`${this.appsettings.getApiURL()}/api/auth/revoke`, token, { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }).pipe(catchError((error: any) => { + error.error = null; + throw error; + })).subscribe(() => { + this.isLoggedIn = false; + localStorage.removeItem('jwt'); + localStorage.removeItem('rjwt'); + this.router.navigate(['/auth/login']); + }); + } + + return null } - private async _isUserLoggedInAsync(): Promise { + async isUserLoggedInAsync(): Promise { const token = this.getToken(); + if (!token || !token.refreshToken) { this.isLoggedIn = false; return false; } - if (token.token && !this.jwtHelper.isTokenExpired(token.token)) { + const verifiedLogin = await firstValueFrom(await this.verifyLogin()); + + if (verifiedLogin) { this.isLoggedIn = true; return true; } - if (this.isLoggedIn !== false) { + if (this.isLoggedIn) { this.spinnerService.showSpinner(); - const resfreshedToken = await this.refresh(token) - .pipe(catchError((err: Error) => { - this.logout(); - this.spinnerService.hideSpinner(); - throw err; - })) - .toPromise(); + + const resfreshedToken = await firstValueFrom(await this.refresh(token)); + this.spinnerService.hideSpinner(); if (resfreshedToken) { this.saveToken(resfreshedToken); return true; } } - this.isLoggedIn = false; + return false; } diff --git a/kdb-web/src/app/services/error-handler/error-handler.service.ts b/kdb-web/src/app/services/error-handler/error-handler.service.ts index ac8bb59afb..b2ed4f6d02 100644 --- a/kdb-web/src/app/services/error-handler/error-handler.service.ts +++ b/kdb-web/src/app/services/error-handler/error-handler.service.ts @@ -2,6 +2,8 @@ import { HttpErrorResponse } from '@angular/common/http'; import { ErrorHandler, Injectable, Injector } from '@angular/core'; import { Observable, throwError } from 'rxjs'; import { ErrorDTO } from 'src/app/models/error/error-dto'; +import { ServiceErrorCode } from 'src/app/models/error/service-error-code.enum'; +import { AuthService } from '../auth/auth.service'; import { ToastService } from '../toast/toast.service'; @Injectable() @@ -17,6 +19,11 @@ export class ErrorHandlerService implements ErrorHandler { let header = 'Fehler'; const errorDto: ErrorDTO = error.error; + if (errorDto.errorCode === ServiceErrorCode.Unauthorized) { + this.injector.get(AuthService).logout(); + return throwError(() => error); + } + if (errorDto.errorCode !== undefined) { header = 'Fehlercode: ' + errorDto.errorCode; } @@ -29,6 +36,10 @@ export class ErrorHandlerService implements ErrorHandler { this.injector.get(ToastService).error(header, message); } - return throwError(error); + + if (error.status === 401) { + this.injector.get(AuthService).logout(); + } + return throwError(() => error); } } diff --git a/kdb-web/src/app/services/settings/settings.service.ts b/kdb-web/src/app/services/settings/settings.service.ts index 17c292d1bd..ec2b93d737 100644 --- a/kdb-web/src/app/services/settings/settings.service.ts +++ b/kdb-web/src/app/services/settings/settings.service.ts @@ -22,7 +22,7 @@ export class SettingsService { this.http.get('../../assets/config.json') .pipe(catchError(error => { reject(error); - return throwError(error); + return throwError(() => error); })).subscribe(settings => { this.appsettings = settings; resolve(settings); diff --git a/kdb-web/src/app/services/theme/theme.service.ts b/kdb-web/src/app/services/theme/theme.service.ts index 47537679db..f83a4510d1 100644 --- a/kdb-web/src/app/services/theme/theme.service.ts +++ b/kdb-web/src/app/services/theme/theme.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs'; import { Themes } from 'src/app/models/view/themes.enum'; import { AuthService } from '../auth/auth.service'; @@ -13,9 +14,14 @@ export class ThemeService { isSidebarOpen = false; hasLangChanged = false; + isSidebarOpen$ = new Subject(); + constructor( private authService: AuthService ) { + this.isSidebarOpen$.subscribe(isSidebarOpen => { + this.isSidebarOpen = isSidebarOpen + }); this.loadTheme(); this.loadMenu(); this.themeName = null; @@ -93,6 +99,6 @@ export class ThemeService { setSideWidth($event: any): void { this.sidebarWidth = $event ? '150px' : '50px'; - this.isSidebarOpen = $event; + this.isSidebarOpen$.next($event) } }