Added verify-login #70

This commit is contained in:
Sven Heidemann 2022-10-16 12:06:18 +02:00
parent 3fe8e1503c
commit ba881aefa8
9 changed files with 93 additions and 47 deletions

View File

@ -6,6 +6,8 @@ from flask import request, jsonify, Response
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.api import Api 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.filter.auth_user_select_criteria import AuthUserSelectCriteria
from bot_api.json_processor import JSONProcessor from bot_api.json_processor import JSONProcessor
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
@ -76,6 +78,19 @@ class AuthController:
result = await self._auth_service.login_async(dto) result = await self._auth_service.login_async(dto)
return jsonify(result.to_dict()) 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/<email>') @Route.post(f'{BasePath}/forgot-password/<email>')
async def forgot_password(self, email: str): async def forgot_password(self, email: str):
await self._auth_service.forgot_password_async(email) await self._auth_service.forgot_password_async(email)
@ -108,7 +123,6 @@ class AuthController:
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.post(f'{BasePath}/revoke') @Route.post(f'{BasePath}/revoke')
@Route.authorize
async def revoke(self): async def revoke(self):
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
await self._auth_service.revoke_async(dto) await self._auth_service.revoke_async(dto)

View File

@ -1,5 +1,7 @@
from enum import Enum from enum import Enum
from werkzeug.exceptions import Unauthorized
class ServiceErrorCode(Enum): class ServiceErrorCode(Enum):
@ -17,3 +19,5 @@ class ServiceErrorCode(Enum):
ConnectionFailed = 8 ConnectionFailed = 8
Timeout = 9 Timeout = 9
MailError = 10 MailError = 10
Unauthorized = 11

View File

@ -1,12 +1,13 @@
from functools import wraps from functools import wraps
from typing import Optional from typing import Optional
from flask import request from flask import request, jsonify
from flask_cors import cross_origin from flask_cors import cross_origin
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.exception.service_exception import ServiceException 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 from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
@ -30,14 +31,20 @@ class Route:
bearer = request.headers.get('Authorization') bearer = request.headers.get('Authorization')
token = bearer.split()[1] token = bearer.split()[1]
if not token: if token is None:
raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set') 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: 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): 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) return await f(*args, **kwargs)

View File

@ -12,5 +12,6 @@ export enum ServiceErrorCode {
ConnectionFailed = 8, ConnectionFailed = 8,
Timeout = 9, Timeout = 9,
MailError = 10 MailError = 10,
Unauthorized = 11
} }

View File

@ -136,7 +136,7 @@ export class AuthUserComponent implements OnInit {
loadNextPage() { loadNextPage() {
this.authService.getFilteredUsers(this.searchCriterions).pipe(catchError(err => { this.authService.getFilteredUsers(this.searchCriterions).pipe(catchError(err => {
this.loading = false; this.loading = false;
return throwError(err); return throwError(() => err);
})).subscribe(list => { })).subscribe(list => {
this.totalRecords = list.totalCount; this.totalRecords = list.totalCount;
this.users = list.users; this.users = list.users;

View File

@ -2,7 +2,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt'; import { JwtHelperService } from '@auth0/angular-jwt';
import { Observable, Subscription } from 'rxjs'; import { firstValueFrom, Observable, Subscription } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { AdminUpdateUserDTO } from 'src/app/models/auth/admin-update-user.dto'; import { AdminUpdateUserDTO } from 'src/app/models/auth/admin-update-user.dto';
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum'; import { AuthRoles } from 'src/app/models/auth/auth-roles.enum';
@ -91,6 +91,14 @@ export class AuthService {
}); });
} }
verifyLogin(): Observable<boolean> {
return this.http.get<boolean>(`${this.appsettings.getApiURL()}/api/auth/verify-login`, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
});
}
forgotPassword(email: string): Observable<unknown> { forgotPassword(email: string): Observable<unknown> {
const emailJson = JSON.stringify(email); const emailJson = JSON.stringify(email);
return this.http.post(`${this.appsettings.getApiURL()}/api/auth/forgot-password`, emailJson, { 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<TokenDTO>(`${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) { deleteUserByMail(mail: string) {
return this.http.post(`${this.appsettings.getApiURL()}/api/auth/delete-user-by-mail/${mail}`, { return this.http.post(`${this.appsettings.getApiURL()}/api/auth/delete-user-by-mail/${mail}`, {
headers: new HttpHeaders({ headers: new HttpHeaders({
@ -190,39 +177,55 @@ export class AuthService {
return this.jwtHelper.decodeToken(this.getToken().token); return this.jwtHelper.decodeToken(this.getToken().token);
} }
async isUserLoggedInAsync(): Promise<boolean> { logout(): Subscription | null {
this.isLoggedIn = await this._isUserLoggedInAsync(); const token = this.getToken();
return this.isLoggedIn;
if (token && token.token && token.refreshToken) {
return this.http.post<TokenDTO>(`${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']);
});
} }
private async _isUserLoggedInAsync(): Promise<boolean> { return null
}
async isUserLoggedInAsync(): Promise<boolean> {
const token = this.getToken(); const token = this.getToken();
if (!token || !token.refreshToken) { if (!token || !token.refreshToken) {
this.isLoggedIn = false; this.isLoggedIn = false;
return false; return false;
} }
if (token.token && !this.jwtHelper.isTokenExpired(token.token)) { const verifiedLogin = await firstValueFrom(await this.verifyLogin());
if (verifiedLogin) {
this.isLoggedIn = true; this.isLoggedIn = true;
return true; return true;
} }
if (this.isLoggedIn !== false) { if (this.isLoggedIn) {
this.spinnerService.showSpinner(); this.spinnerService.showSpinner();
const resfreshedToken = await this.refresh(token)
.pipe(catchError((err: Error) => { const resfreshedToken = await firstValueFrom(await this.refresh(token));
this.logout();
this.spinnerService.hideSpinner();
throw err;
}))
.toPromise();
this.spinnerService.hideSpinner(); this.spinnerService.hideSpinner();
if (resfreshedToken) { if (resfreshedToken) {
this.saveToken(resfreshedToken); this.saveToken(resfreshedToken);
return true; return true;
} }
} }
this.isLoggedIn = false;
return false; return false;
} }

View File

@ -2,6 +2,8 @@ import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable, Injector } from '@angular/core'; import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { Observable, throwError } from 'rxjs'; import { Observable, throwError } from 'rxjs';
import { ErrorDTO } from 'src/app/models/error/error-dto'; 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'; import { ToastService } from '../toast/toast.service';
@Injectable() @Injectable()
@ -17,6 +19,11 @@ export class ErrorHandlerService implements ErrorHandler {
let header = 'Fehler'; let header = 'Fehler';
const errorDto: ErrorDTO = error.error; const errorDto: ErrorDTO = error.error;
if (errorDto.errorCode === ServiceErrorCode.Unauthorized) {
this.injector.get(AuthService).logout();
return throwError(() => error);
}
if (errorDto.errorCode !== undefined) { if (errorDto.errorCode !== undefined) {
header = 'Fehlercode: ' + errorDto.errorCode; header = 'Fehlercode: ' + errorDto.errorCode;
} }
@ -29,6 +36,10 @@ export class ErrorHandlerService implements ErrorHandler {
this.injector.get(ToastService).error(header, message); this.injector.get(ToastService).error(header, message);
} }
return throwError(error);
if (error.status === 401) {
this.injector.get(AuthService).logout();
}
return throwError(() => error);
} }
} }

View File

@ -22,7 +22,7 @@ export class SettingsService {
this.http.get<Appsettings>('../../assets/config.json') this.http.get<Appsettings>('../../assets/config.json')
.pipe(catchError(error => { .pipe(catchError(error => {
reject(error); reject(error);
return throwError(error); return throwError(() => error);
})).subscribe(settings => { })).subscribe(settings => {
this.appsettings = settings; this.appsettings = settings;
resolve(settings); resolve(settings);

View File

@ -1,4 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { Themes } from 'src/app/models/view/themes.enum'; import { Themes } from 'src/app/models/view/themes.enum';
import { AuthService } from '../auth/auth.service'; import { AuthService } from '../auth/auth.service';
@ -13,9 +14,14 @@ export class ThemeService {
isSidebarOpen = false; isSidebarOpen = false;
hasLangChanged = false; hasLangChanged = false;
isSidebarOpen$ = new Subject<boolean>();
constructor( constructor(
private authService: AuthService private authService: AuthService
) { ) {
this.isSidebarOpen$.subscribe(isSidebarOpen => {
this.isSidebarOpen = isSidebarOpen
});
this.loadTheme(); this.loadTheme();
this.loadMenu(); this.loadMenu();
this.themeName = null; this.themeName = null;
@ -93,6 +99,6 @@ export class ThemeService {
setSideWidth($event: any): void { setSideWidth($event: any): void {
this.sidebarWidth = $event ? '150px' : '50px'; this.sidebarWidth = $event ? '150px' : '50px';
this.isSidebarOpen = $event; this.isSidebarOpen$.next($event)
} }
} }