Fix login and group user login. Hides add buttons when the user doesn't have the required permission.
continuous-integration/drone/pr Build was killed Details

This commit is contained in:
FyloZ 2021-12-14 23:38:32 -05:00
parent cb7f38b46b
commit be592a22f5
Signed by: william
GPG Key ID: 835378AE9AF4AE97
13 changed files with 95 additions and 114 deletions

View File

@ -3,7 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json", "start": "ng serve --host 0.0.0.0 --proxy-config proxy.conf.json",
"build": "ng build", "build": "ng build",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",

View File

@ -48,17 +48,10 @@ export class Login extends ErrorHandlingComponent {
} }
submit() { submit() {
this.subscribe( this.subscribeAndNavigate(
this.accountService.login(this.userIdControl.value, this.passwordControl.value), this.accountService.login(this.userIdControl.value, this.passwordControl.value),
() => {} '/color/list'
) )
// Does not use SubscribingComponent shortcut because backend doesn't return expected error type
// this.accountService.login(this.userIdControl.value, this.passwordControl.value)
// .pipe(take(1), takeUntil(this.destroy$))
// .subscribe({
// next: () => this.urlUtils.navigateTo('/color'),
// error: error => this.handleLoginError(error)
// })
} }
get controls(): { userId: FormControl, password: FormControl } { get controls(): { userId: FormControl, password: FormControl } {

View File

@ -31,7 +31,7 @@ export class LoginComponent extends ErrorHandlingComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.errorService.activeErrorHandler = this this.errorService.activeErrorHandler = this
if (this.accountService.isLoggedIn()) { if (this.appState.isAuthenticated) {
this.router.navigate(['/color']) this.router.navigate(['/color'])
} }

View File

@ -1,28 +1,35 @@
import {Component, OnInit} from '@angular/core'; import {Component} from '@angular/core';
import {AccountService} from "../../services/account.service"; import {AccountService} from "../../services/account.service";
import {Router} from "@angular/router"; import {ActivatedRoute, Router} from "@angular/router";
import {AppState} from "../../../shared/app-state";
import {SubscribingComponent} from "../../../shared/components/subscribing.component";
import {ErrorService} from "../../../shared/service/error.service";
@Component({ @Component({
selector: 'cre-logout', selector: 'cre-logout',
templateUrl: './logout.component.html', templateUrl: './logout.component.html',
styleUrls: ['./logout.component.sass'] styleUrls: ['./logout.component.sass']
}) })
export class LogoutComponent implements OnInit { export class LogoutComponent extends SubscribingComponent {
constructor( constructor(
private accountService: AccountService, private accountService: AccountService,
private router: Router private appState: AppState,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) { ) {
super(errorService, activatedRoute, router)
} }
ngOnInit(): void { ngOnInit(): void {
if (!this.accountService.isLoggedIn()) { if (!this.appState.isAuthenticated) {
this.router.navigate(['/account/login']) this.urlUtils.navigateTo('/account/login')
} }
this.accountService.logout(() => { this.subscribeAndNavigate(
this.router.navigate(['/account/login']) this.accountService.logout(),
}) '/account/login'
)
} }
} }

View File

@ -5,7 +5,7 @@ import {AppState} from '../../shared/app-state'
import {HttpClient, HttpResponse} from '@angular/common/http' import {HttpClient, HttpResponse} from '@angular/common/http'
import {environment} from '../../../../environments/environment' import {environment} from '../../../../environments/environment'
import {ApiService} from '../../shared/service/api.service' import {ApiService} from '../../shared/service/api.service'
import {Permission} from '../../shared/model/user' import {Permission, User} from '../../shared/model/user'
import {ErrorService} from '../../shared/service/error.service' import {ErrorService} from '../../shared/service/error.service'
import {AlertService} from '../../shared/service/alert.service' import {AlertService} from '../../shared/service/alert.service'
import {JwtService} from "./jwt.service"; import {JwtService} from "./jwt.service";
@ -31,28 +31,24 @@ export class AccountService implements OnDestroy {
this.destroy$.complete() this.destroy$.complete()
} }
isLoggedIn(): boolean {
return this.appState.isAuthenticated
}
checkAuthenticationStatus() { checkAuthenticationStatus() {
if (!this.appState.authenticatedUser) { if (!this.appState.isAuthenticated) {
// Try to get current default group user // Try to get current default group user
// this.http.get<User>(`${environment.apiUrl}/user/current`, {withCredentials: true}) this.http.get<User>(`${environment.apiUrl}/user/group/currentuser`, {withCredentials: true})
// .pipe( .pipe(
// take(1), take(1),
// takeUntil(this.destroy$), takeUntil(this.destroy$),
// ).subscribe( ).subscribe(
// { {
// next: user => this.appState.authenticatedUser = user, next: user => this.appState.authenticateGroupUser(user),
// error: err => { error: err => {
// if (err.status === 404 || err.status === 403) { if (err.status === 404 || err.status === 403) {
// console.warn('No default user is defined on this computer') console.warn('No default user is defined on this computer')
// } else { } else {
// this.errorService.handleError(err) this.errorService.handleError(err)
// } }
// } }
// }) })
} }
} }
@ -89,44 +85,41 @@ export class AccountService implements OnDestroy {
private loginUser(response: HttpResponse<void>) { private loginUser(response: HttpResponse<void>) {
const authorization = response.headers.get("Authorization") const authorization = response.headers.get("Authorization")
const jwt = this.jwtService.parseJwt(authorization) const user = this.jwtService.parseUser(authorization)
this.appState.authenticateUser(jwt) this.appState.authenticateUser(user)
} }
logout(success: () => void) { logout(): Observable<void> {
this.api.get<void>('/logout', true).pipe( const subject = new Subject<void>()
this.api.get<void>('/logout').pipe(
take(1), take(1),
takeUntil(this.destroy$) takeUntil(this.destroy$)
) ).subscribe({
.subscribe({ next: () => {
next: () => { this.logoutUser()
this.appState.resetAuthenticatedUser()
this.checkAuthenticationStatus() subject.next()
success() subject.complete()
}, },
error: err => this.errorService.handleError(err) error: error => {
}) this.errorService.handleError(error)
subject.next()
subject.complete()
}
})
return subject
}
private logoutUser() {
this.appState.resetAuthenticatedUser()
this.checkAuthenticationStatus()
} }
hasPermission(permission: Permission): boolean { hasPermission(permission: Permission): boolean {
return this.appState.authenticatedUser && this.appState.authenticatedUser.permissions.indexOf(permission) >= 0 return this.appState.authenticatedUser && this.appState.authenticatedUser.permissions.indexOf(permission) >= 0
} }
private setLoggedInUserFromApi() {
// this.api.get<User>('/user/current', true)
// .pipe(
// take(1),
// takeUntil(this.destroy$)
// )
// .subscribe({
// next: user => {
// this.appState.authenticatedUser = user
// At this point the loading wheel should be visible
// globalLoadingWheel.hide()
// },
// error: err => this.errorService.handleError(err)
// })
console.warn("REMOVE THIS")
}
} }

View File

@ -1,19 +1,13 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import jwtDecode from "jwt-decode"; import jwtDecode from "jwt-decode";
import {parseJson} from "@angular/cli/utilities/json-file"; import { User } from "../../shared/model/user";
import {CreJwt} from "../../shared/app-state";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class JwtService { export class JwtService {
parseJwt(jwt: string): CreJwt { parseUser(jwt: string): User {
const decoded = jwtDecode(jwt) as any const decoded = jwtDecode(jwt) as any
return JSON.parse(decoded.user)
return {
sub: decoded.sub,
exp: decoded.exp,
user: parseJson(decoded.user)
}
} }
} }

View File

@ -1,6 +1,6 @@
<cre-action-bar [reverse]="true"> <cre-action-bar [reverse]="true">
<cre-action-group> <cre-action-group>
<cre-accent-button routerLink="/catalog/company/add">Ajouter</cre-accent-button> <cre-accent-button *ngIf="hasEditPermission" routerLink="/catalog/company/add">Ajouter</cre-accent-button>
</cre-action-group> </cre-action-group>
</cre-action-bar> </cre-action-bar>

View File

@ -1,6 +1,6 @@
<cre-action-bar [reverse]="true"> <cre-action-bar [reverse]="true">
<cre-action-group> <cre-action-group>
<cre-accent-button routerLink="/catalog/materialtype/add">Ajouter</cre-accent-button> <cre-accent-button *ngIf="hasEditPermission" routerLink="/catalog/materialtype/add">Ajouter</cre-accent-button>
</cre-action-group> </cre-action-group>
</cre-action-bar> </cre-action-bar>

View File

@ -46,7 +46,7 @@ export class RecipeForm extends SubscribingComponent {
} }
ngOnInit() { ngOnInit() {
super.ngOnInit(); super.ngOnInit()
this.fetchCompanies() this.fetchCompanies()

View File

@ -2,14 +2,13 @@ import {Injectable} from '@angular/core'
import {User} from './model/user' import {User} from './model/user'
import {Subject} from 'rxjs' import {Subject} from 'rxjs'
import {Title} from '@angular/platform-browser' import {Title} from '@angular/platform-browser'
import jwtDecode from "jwt-decode";
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AppState { export class AppState {
private readonly KEY_AUTHENTICATED = 'authenticated' private readonly KEY_AUTHENTICATED = 'authenticated'
private readonly KEY_AUTHENTICATION_EXPIRATION = 'authentication-expiration' private readonly KEY_DEFAULT_GROUP_USER_AUTHENTICATED = 'default-group-user-authenticated'
private readonly KEY_LOGGED_IN_USER = 'logged-in-user' private readonly KEY_LOGGED_IN_USER = 'logged-in-user'
authenticatedUser$ = new Subject<{ authenticated: boolean, authenticatedUser: User }>() authenticatedUser$ = new Subject<{ authenticated: boolean, authenticatedUser: User }>()
@ -20,15 +19,19 @@ export class AppState {
) { ) {
} }
authenticateUser(jwt: CreJwt) { authenticateUser(user: User) {
this.authenticatedUser = jwt.user this.authenticatedUser = user
this.authenticationExpiration = jwt.exp
this.isAuthenticated = true this.isAuthenticated = true
} }
authenticateGroupUser(user: User) {
this.authenticatedUser = user
this.isDefaultGroupUserAuthenticated = true
}
resetAuthenticatedUser() { resetAuthenticatedUser() {
this.isAuthenticated = false this.isAuthenticated = false
this.authenticationExpiration = -1 this.isDefaultGroupUserAuthenticated = false
this.authenticatedUser = null this.authenticatedUser = null
} }
@ -51,13 +54,16 @@ export class AppState {
}) })
} }
get authenticationExpiration(): number { get isDefaultGroupUserAuthenticated(): boolean {
return parseInt(sessionStorage.getItem(this.KEY_AUTHENTICATION_EXPIRATION)) return sessionStorage.getItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED) === 'true'
} }
private set authenticationExpiration(value: number) { private set isDefaultGroupUserAuthenticated(value: boolean) {
console.error(value) sessionStorage.setItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED, value.toString())
sessionStorage.setItem(this.KEY_AUTHENTICATION_EXPIRATION, value.toString()) }
get hasCredentials(): boolean {
return this.isAuthenticated || this.isDefaultGroupUserAuthenticated
} }
get authenticatedUser(): User { get authenticatedUser(): User {
@ -81,9 +87,3 @@ export class AppState {
this.titleService.setTitle(`CRE: ${value}`) this.titleService.setTitle(`CRE: ${value}`)
} }
} }
export interface CreJwt {
readonly sub: string
readonly exp: number,
readonly user: User
}

View File

@ -58,9 +58,10 @@ export class HeaderComponent extends SubscribingComponent {
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.accountService.logout(() => { this.subscribe(
console.log('Successfully logged out') this.accountService.logout(),
}) () => console.info('Successfully logged out')
)
super.ngOnDestroy() super.ngOnDestroy()
} }
@ -71,7 +72,7 @@ export class HeaderComponent extends SubscribingComponent {
set activeLink(link: string) { set activeLink(link: string) {
this._activeLink = link this._activeLink = link
this.router.navigate([link]) this.urlUtils.navigateTo(link)
} }
get activeLink() { get activeLink() {

View File

@ -70,14 +70,13 @@ export class ApiService implements OnDestroy {
observe: 'response' observe: 'response'
} }
if (needAuthentication) { if (needAuthentication) {
if (this.checkAuthenticated()) { if (this.appState.hasCredentials) {
if (httpOptions) { if (httpOptions) {
httpOptions.withCredentials = true httpOptions.withCredentials = true
} else { } else {
console.error('httpOptions need to be specified to use credentials in HTTP methods.') console.error('httpOptions need to be specified to use credentials in HTTP methods.')
} }
} else { } else {
// this.appState.resetAuthenticatedUser()
this.navigateToLogin() this.navigateToLogin()
} }
} }
@ -89,12 +88,6 @@ export class ApiService implements OnDestroy {
.pipe(takeUntil(this._destroy$), map(r => r.body), share()) .pipe(takeUntil(this._destroy$), map(r => r.body), share())
} }
private checkAuthenticated(): boolean {
console.log(Date.now() / 1000, this.appState.authenticationExpiration)
return (this.appState.isAuthenticated && Date.now() <= this.appState.authenticationExpiration) ||
(this.appState.authenticatedUser && this.appState.authenticatedUser.group != null)
}
private navigateToLogin() { private navigateToLogin() {
this.router.navigate(['/account/login']) this.router.navigate(['/account/login'])
} }

View File

@ -1,7 +1,7 @@
<cre-action-bar> <cre-action-bar>
<cre-action-group></cre-action-group> <cre-action-group></cre-action-group>
<cre-action-group> <cre-action-group>
<cre-accent-button routerLink="/misc/touch-up-kit/add">Ajouter</cre-accent-button> <cre-accent-button *ngIf="canEditTouchUpKits" routerLink="/misc/touch-up-kit/add">Ajouter</cre-accent-button>
</cre-action-group> </cre-action-group>
</cre-action-bar> </cre-action-bar>