feature/30-group-authentication #9
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<LoginDto> {
|
||||
const login$ = this.http.post<LoginDto>(
|
||||
`${environment.apiUrl}/account/login`,
|
||||
{id: userId, password},
|
||||
{withCredentials: true})
|
||||
loginAsGroupIfNotAuthenticated() {
|
||||
if (this.appState.isAuthenticated) return
|
||||
|
||||
this.loginAsGroup()
|
||||
}
|
||||
|
||||
loginAsUser(id: number, password: string): Observable<LoginDto> {
|
||||
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<LoginDto> {
|
||||
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<LoginDto> {
|
||||
let url = `${environment.apiUrl}/account/login`
|
||||
if (isGroup) {
|
||||
url += '/group'
|
||||
}
|
||||
|
||||
const login$ = this.http.post<LoginDto>(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<AccountModel>(`${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<any> {
|
||||
// const subject = new Subject<void>()
|
||||
//
|
||||
// this.http.post<any>(`${environment.apiUrl}/login`, {id: userId, password}, {
|
||||
// withCredentials: true,
|
||||
// observe: 'response' as 'body'
|
||||
// }).pipe(
|
||||
// take(1),
|
||||
// takeUntil(this.destroy$)
|
||||
// ).subscribe({
|
||||
// next: (response: HttpResponse<void>) => {
|
||||
// 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<void>) {
|
||||
const authorization = response.headers.get("Authorization")
|
||||
const user = this.jwtService.parseUser(authorization)
|
||||
|
||||
this.appState.authenticateUser(user)
|
||||
}
|
||||
|
||||
logout(): Observable<void> {
|
||||
const subject = new Subject<void>()
|
||||
|
||||
this.api.get<void>('/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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<number, RecipeStep[]>): boolean {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<labeled-icon
|
||||
*ngIf="authenticated"
|
||||
icon="account"
|
||||
label="{{user.firstName}} {{user.lastName}}">
|
||||
label="{{user.fullName}}">
|
||||
</labeled-icon>
|
||||
<div class="d-flex flex-row">
|
||||
<labeled-icon
|
||||
|
@ -15,7 +15,7 @@
|
|||
*ngIf="userInGroup"
|
||||
class="user-info-group"
|
||||
icon="account-multiple"
|
||||
[label]="user.group.name">
|
||||
[label]="user.groupName">
|
||||
</labeled-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue