From 3ff897fc859b82189b5acca850ae4ec476797973 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 12 May 2022 07:45:18 -0400 Subject: [PATCH] #30 Update login to be compatible with backend --- src/app/modules/accounts/accounts.ts | 15 +- .../accounts/services/account.service.ts | 142 ++++++------------ src/app/modules/recipes/explore.ts | 2 +- src/app/modules/recipes/recipes.ts | 2 +- src/app/modules/shared/app-state.ts | 4 +- .../components/header/header.component.ts | 8 +- .../shared/components/nav/nav.component.ts | 6 +- .../components/subscribing.component.ts | 4 +- .../user-info/user-menu.component.html | 4 +- .../user-info/user-menu.component.ts | 8 +- .../modules/shared/service/error.service.ts | 31 ++-- 11 files changed, 89 insertions(+), 137 deletions(-) diff --git a/src/app/modules/accounts/accounts.ts b/src/app/modules/accounts/accounts.ts index 261ed61..fccec7d 100644 --- a/src/app/modules/accounts/accounts.ts +++ b/src/app/modules/accounts/accounts.ts @@ -47,9 +47,9 @@ export class Login extends ErrorHandlingComponent { } submit() { - this.subscribe( - this.accountService.login(this.userIdControl.value, this.passwordControl.value), - next => {} + this.subscribeAndNavigate( + this.accountService.loginAsUser(this.userIdControl.value, this.passwordControl.value), + '/color/list' ) } @@ -79,13 +79,10 @@ export class Logout extends SubscribingComponent { } ngOnInit(): void { - if (!this.appState.isAuthenticated) { - this.urlUtils.navigateTo('/account/login') + if (this.appState.isAuthenticated) { + this.accountService.logout() } - this.subscribeAndNavigate( - this.accountService.logout(), - '/account/login' - ) + this.urlUtils.navigateTo('/account/login') } } diff --git a/src/app/modules/accounts/services/account.service.ts b/src/app/modules/accounts/services/account.service.ts index 0c02921..97d6a10 100644 --- a/src/app/modules/accounts/services/account.service.ts +++ b/src/app/modules/accounts/services/account.service.ts @@ -2,12 +2,11 @@ import {Injectable, OnDestroy} from '@angular/core' import {Observable, Subject} from 'rxjs' import {take, takeUntil} from 'rxjs/operators' import {AppState} from '../../shared/app-state' -import {HttpClient, HttpResponse} from '@angular/common/http' +import {HttpClient} from '@angular/common/http' import {environment} from '../../../../environments/environment' import {ApiService} from '../../shared/service/api.service' -import {Permission, AccountModel, LoginDto} from '../../shared/model/account.model' +import {LoginDto, Permission} from '../../shared/model/account.model' import {ErrorService} from '../../shared/service/error.service' -import {AlertService} from '../../shared/service/alert.service' import {JwtService} from "./jwt.service"; @Injectable({ @@ -21,8 +20,7 @@ export class AccountService implements OnDestroy { private api: ApiService, private appState: AppState, private jwtService: JwtService, - private errorService: ErrorService, - private alertService: AlertService + private errorService: ErrorService ) { } @@ -31,112 +29,58 @@ export class AccountService implements OnDestroy { this.destroy$.complete() } - login(userId: number, password: string): Observable { - const login$ = this.http.post( - `${environment.apiUrl}/account/login`, - {id: userId, password}, - {withCredentials: true}) + loginAsGroupIfNotAuthenticated() { + if (this.appState.isAuthenticated) return + + this.loginAsGroup() + } + + loginAsUser(id: number, password: string): Observable { + return this.login(false, {id, password}, error => { + if (error.status !== 403) return false + + this.errorService.handleError({status: error.status, type: 'invalid-credentials', obj: error}) + return true + }) + } + + loginAsGroup(): Observable { + return this.login(true, {}, + error => error.status === 403) // There is no group token, so do nothing + } + + private login(isGroup: boolean, body: LoginBody, errorConsumer: (any) => boolean): Observable { + let url = `${environment.apiUrl}/account/login` + if (isGroup) { + url += '/group' + } + + const login$ = this.http.post(url, body, {withCredentials: true}) .pipe(take(1), takeUntil(this.destroy$)) login$.subscribe({ - next: result => { - console.log(result) - }, - error: err => this.errorService.handleError(err) + next: result => this.appState.authenticateUser(result), + error: error => { + if (errorConsumer(error)) return; + + this.errorService.handleErrorResponse(error) + } }) return login$ } - checkAuthenticationStatus() { - if (!this.appState.isAuthenticated) { - // Try to get current default group user - // this.http.get(`${environment.apiUrl}/user/group/currentuser`, {withCredentials: true}) - // .pipe( - // take(1), - // takeUntil(this.destroy$), - // ).subscribe( - // { - // next: user => this.appState.authenticateGroupUser(user), - // error: err => { - // if (err.status === 404 || err.status === 403) { - // console.warn('No default user is defined on this computer') - // } else { - // this.errorService.handleError(err) - // } - // } - // }) - } - } - - // login(userId: number, password: string): Observable { - // const subject = new Subject() - // - // this.http.post(`${environment.apiUrl}/login`, {id: userId, password}, { - // withCredentials: true, - // observe: 'response' as 'body' - // }).pipe( - // take(1), - // takeUntil(this.destroy$) - // ).subscribe({ - // next: (response: HttpResponse) => { - // this.loginUser(response) - // - // subject.next() - // subject.complete() - // }, - // error: error => { - // if (error.status === 403) { - // this.alertService.pushError('Les identifiants entrés sont invalides') - // } else { - // this.errorService.handleError(error) - // } - // - // subject.next() - // subject.complete() - // } - // }) - // - // return subject - // } - - private loginUser(response: HttpResponse) { - const authorization = response.headers.get("Authorization") - const user = this.jwtService.parseUser(authorization) - - this.appState.authenticateUser(user) - } - - logout(): Observable { - const subject = new Subject() - - this.api.get('/logout').pipe( - take(1), - takeUntil(this.destroy$) - ).subscribe({ - next: () => { - this.logoutUser() - - subject.next() - subject.complete() - }, - error: error => { - this.errorService.handleError(error) - - subject.next() - subject.complete() - } - }) - - return subject - } - - private logoutUser() { + logout() { this.appState.resetAuthenticatedUser() - this.checkAuthenticationStatus() + this.loginAsGroupIfNotAuthenticated() } hasPermission(permission: Permission): boolean { return this.appState.authenticatedUser && this.appState.authenticatedUser.permissions.indexOf(permission) >= 0 } } + +interface LoginBody { + id?: number + password?: string +} diff --git a/src/app/modules/recipes/explore.ts b/src/app/modules/recipes/explore.ts index b6a1ea0..fb00c96 100644 --- a/src/app/modules/recipes/explore.ts +++ b/src/app/modules/recipes/explore.ts @@ -175,7 +175,7 @@ export class CreRecipeExplore extends ErrorHandlingComponent { } get loggedInUserGroupId(): number { - return this.appState.authenticatedUser.group?.id + return this.appState.authenticatedUser.groupId } get selectedGroupNote(): string { diff --git a/src/app/modules/recipes/recipes.ts b/src/app/modules/recipes/recipes.ts index dcc6a9a..046a9b9 100644 --- a/src/app/modules/recipes/recipes.ts +++ b/src/app/modules/recipes/recipes.ts @@ -202,7 +202,7 @@ export class RecipeEdit extends ErrorHandlingComponent { } get loggedInUserGroupId(): number { - return this.appState.authenticatedUser.group?.id + return this.appState.authenticatedUser.groupId } private stepsPositionsAreValid(steps: Map): boolean { diff --git a/src/app/modules/shared/app-state.ts b/src/app/modules/shared/app-state.ts index 02f7e1f..d15d617 100644 --- a/src/app/modules/shared/app-state.ts +++ b/src/app/modules/shared/app-state.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core' -import {AccountModel, LoginDto} from './model/account.model' +import {LoginDto} from './model/account.model' import {Subject} from 'rxjs' import {Title} from '@angular/platform-browser' @@ -73,7 +73,7 @@ export class AppState { private set authenticatedUser(value: LoginDto) { if (value === null) { - // sessionStorage.removeItem(this.KEY_LOGGED_IN_USER) + sessionStorage.removeItem(this.KEY_LOGGED_IN_USER) } else { sessionStorage.setItem(this.KEY_LOGGED_IN_USER, JSON.stringify(value)) } diff --git a/src/app/modules/shared/components/header/header.component.ts b/src/app/modules/shared/components/header/header.component.ts index 275bc29..f64ea8a 100644 --- a/src/app/modules/shared/components/header/header.component.ts +++ b/src/app/modules/shared/components/header/header.component.ts @@ -35,7 +35,7 @@ export class HeaderComponent extends SubscribingComponent { ngOnInit(): void { super.ngOnInit() - this.accountService.checkAuthenticationStatus() + this.accountService.loginAsGroupIfNotAuthenticated() // Gets the current route this.subscribe( @@ -58,10 +58,8 @@ export class HeaderComponent extends SubscribingComponent { } ngOnDestroy(): void { - this.subscribe( - this.accountService.logout(), - () => console.info('Successfully logged out') - ) + this.accountService.logout() + console.info('Successfully logged out') super.ngOnDestroy() } diff --git a/src/app/modules/shared/components/nav/nav.component.ts b/src/app/modules/shared/components/nav/nav.component.ts index 3695ffc..0a3fdd4 100644 --- a/src/app/modules/shared/components/nav/nav.component.ts +++ b/src/app/modules/shared/components/nav/nav.component.ts @@ -1,7 +1,7 @@ import {Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {AccountModel, Permission} from "../../model/account.model"; +import {LoginDto, Permission} from "../../model/account.model"; import {AccountService} from "../../../accounts/services/account.service"; -import {ActivatedRoute, Router} from "@angular/router"; +import {Router} from "@angular/router"; import {takeUntil} from "rxjs/operators"; import {AppState} from "../../app-state"; import {Subject} from "rxjs"; @@ -54,7 +54,7 @@ export class NavComponent implements OnInit, OnDestroy { return this._activeLink } - private updateEnabledLinks(user: AccountModel) { + private updateEnabledLinks(user: LoginDto) { this.links.forEach(l => { if (l.permission) { l.enabled = user && user.permissions.indexOf(l.permission) >= 0; diff --git a/src/app/modules/shared/components/subscribing.component.ts b/src/app/modules/shared/components/subscribing.component.ts index d003d0a..ee13316 100644 --- a/src/app/modules/shared/components/subscribing.component.ts +++ b/src/app/modules/shared/components/subscribing.component.ts @@ -35,7 +35,7 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy { this.hideLoadingWheel(showWheel) }, error: err => { - this.errorService.handleError(err) + this.errorService.handleErrorResponse(err) this.hideLoadingWheel(showWheel) } })) @@ -61,7 +61,7 @@ export abstract class SubscribingComponent implements OnInit, OnDestroy { }, error: err => { this.hideLoadingWheel(showWheel) - this.errorService.handleError(err) + this.errorService.handleErrorResponse(err) } })) } diff --git a/src/app/modules/shared/components/user-info/user-menu.component.html b/src/app/modules/shared/components/user-info/user-menu.component.html index d758d48..cec59aa 100644 --- a/src/app/modules/shared/components/user-info/user-menu.component.html +++ b/src/app/modules/shared/components/user-info/user-menu.component.html @@ -3,7 +3,7 @@ + label="{{user.fullName}}">
diff --git a/src/app/modules/shared/components/user-info/user-menu.component.ts b/src/app/modules/shared/components/user-info/user-menu.component.ts index 272f166..a0e8b54 100644 --- a/src/app/modules/shared/components/user-info/user-menu.component.ts +++ b/src/app/modules/shared/components/user-info/user-menu.component.ts @@ -1,6 +1,6 @@ import {Component, OnDestroy, OnInit} from '@angular/core' import {AppState} from '../../app-state' -import {AccountModel} from '../../model/account.model' +import {LoginDto} from '../../model/account.model' import {Subject} from 'rxjs' import {takeUntil} from 'rxjs/operators' import {UrlUtils} from '../../utils/url.utils' @@ -13,7 +13,7 @@ import {ActivatedRoute, Router} from '@angular/router' }) export class UserMenuComponent implements OnInit, OnDestroy { authenticated = false - user: AccountModel = null + user: LoginDto = null userInGroup = false menuEnabled = false @@ -52,11 +52,11 @@ export class UserMenuComponent implements OnInit, OnDestroy { this.menuEnabled = false } - private authenticationState(authenticated: boolean, user: AccountModel) { + private authenticationState(authenticated: boolean, user: LoginDto) { this.authenticated = authenticated this.user = user if (this.user != null) { - this.userInGroup = this.user.group != null + this.userInGroup = this.user.groupId != null } } } diff --git a/src/app/modules/shared/service/error.service.ts b/src/app/modules/shared/service/error.service.ts index fd045b2..ad7883e 100644 --- a/src/app/modules/shared/service/error.service.ts +++ b/src/app/modules/shared/service/error.service.ts @@ -38,19 +38,22 @@ export class ErrorService { ) { } - handleError(response: any) { - let matchingModels - + handleErrorResponse(response: any) { if (isServerOfflineError(response)) { this.appState.isServerOnline = false return } - const error = response.error - if (!error || !error.type) { + let error = response.error + if (!isHandledError(error)) { return } + this.handleError({status: error.status, type: error.type, obj: error}) + } + + handleError(error: HandledError) { + let matchingModels if (this.activeHandler) { matchingModels = this.activeHandler.errorHandlers.filter(m => m.filter(error)) // Find error models whose filter matches the current error } else { @@ -71,10 +74,10 @@ export class ErrorService { matchingModels.forEach(m => { if (m.consumer || m.messageProducer) { if (m.consumer) { - m.consumer(error) + m.consumer(error.obj) } if (m.messageProducer) { - this.alertService.pushError(m.messageProducer(error)) + this.alertService.pushError(m.messageProducer(error.obj)) } } else { console.error('An error model has no consumer or message') @@ -106,20 +109,30 @@ export interface ErrorHandlerComponent { /** * An error model define how errors matching its filter will be handled. * - * The consumer will consume matching errors when they occurs. + * The consumer will consume matching errors when they occur. * The message producer returns a string that will be pushed to the alert system. * * To work correctly a model must define at least one handler (consumer or message producer). */ export class ErrorHandler { constructor( - public filter: (error: any) => Boolean, + public filter: (error: HandledError) => Boolean, public consumer?: (error: any) => void, public messageProducer?: (error: any) => String ) { } } +export interface HandledError { + status: number, + type: string, + obj: any // The original object, used to access data in the error +} + function isServerOfflineError(response: any): boolean { return response.status === 0 || response.status === 502 } + +function isHandledError(error: any): error is HandledError { + return true +}