12-user-info-jwt #4

Merged
william merged 5 commits from 12-user-info-jwt into develop 2021-12-15 00:24:15 -05:00
13 changed files with 95 additions and 114 deletions
Showing only changes of commit be592a22f5 - Show all commits

View File

@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"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",
"test": "ng test",
"lint": "ng lint",

View File

@ -48,17 +48,10 @@ export class Login extends ErrorHandlingComponent {
}
submit() {
this.subscribe(
this.subscribeAndNavigate(
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 } {

View File

@ -31,7 +31,7 @@ export class LoginComponent extends ErrorHandlingComponent implements OnInit {
ngOnInit(): void {
this.errorService.activeErrorHandler = this
if (this.accountService.isLoggedIn()) {
if (this.appState.isAuthenticated) {
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 {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({
selector: 'cre-logout',
templateUrl: './logout.component.html',
styleUrls: ['./logout.component.sass']
})
export class LogoutComponent implements OnInit {
export class LogoutComponent extends SubscribingComponent {
constructor(
private accountService: AccountService,
private router: Router
private appState: AppState,
errorService: ErrorService,
router: Router,
activatedRoute: ActivatedRoute
) {
super(errorService, activatedRoute, router)
}
ngOnInit(): void {
if (!this.accountService.isLoggedIn()) {
this.router.navigate(['/account/login'])
if (!this.appState.isAuthenticated) {
this.urlUtils.navigateTo('/account/login')
}
this.accountService.logout(() => {
this.router.navigate(['/account/login'])
})
this.subscribeAndNavigate(
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 {environment} from '../../../../environments/environment'
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 {AlertService} from '../../shared/service/alert.service'
import {JwtService} from "./jwt.service";
@ -31,28 +31,24 @@ export class AccountService implements OnDestroy {
this.destroy$.complete()
}
isLoggedIn(): boolean {
return this.appState.isAuthenticated
}
checkAuthenticationStatus() {
if (!this.appState.authenticatedUser) {
if (!this.appState.isAuthenticated) {
// Try to get current default group user
// this.http.get<User>(`${environment.apiUrl}/user/current`, {withCredentials: true})
// .pipe(
// take(1),
// takeUntil(this.destroy$),
// ).subscribe(
// {
// next: user => this.appState.authenticatedUser = 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)
// }
// }
// })
this.http.get<User>(`${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)
}
}
})
}
}
@ -89,44 +85,41 @@ export class AccountService implements OnDestroy {
private loginUser(response: HttpResponse<void>) {
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) {
this.api.get<void>('/logout', true).pipe(
logout(): Observable<void> {
const subject = new Subject<void>()
this.api.get<void>('/logout').pipe(
take(1),
takeUntil(this.destroy$)
)
.subscribe({
next: () => {
this.appState.resetAuthenticatedUser()
this.checkAuthenticationStatus()
success()
},
error: err => this.errorService.handleError(err)
})
).subscribe({
next: () => {
this.logoutUser()
subject.next()
subject.complete()
},
error: error => {
this.errorService.handleError(error)
subject.next()
subject.complete()
}
})
return subject
}
private logoutUser() {
this.appState.resetAuthenticatedUser()
this.checkAuthenticationStatus()
}
hasPermission(permission: Permission): boolean {
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 jwtDecode from "jwt-decode";
import {parseJson} from "@angular/cli/utilities/json-file";
import {CreJwt} from "../../shared/app-state";
import { User } from "../../shared/model/user";
@Injectable({
providedIn: 'root'
})
export class JwtService {
parseJwt(jwt: string): CreJwt {
parseUser(jwt: string): User {
const decoded = jwtDecode(jwt) as any
return {
sub: decoded.sub,
exp: decoded.exp,
user: parseJson(decoded.user)
}
return JSON.parse(decoded.user)
}
}

View File

@ -1,6 +1,6 @@
<cre-action-bar [reverse]="true">
<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-bar>

View File

@ -1,6 +1,6 @@
<cre-action-bar [reverse]="true">
<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-bar>

View File

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

View File

@ -2,14 +2,13 @@ import {Injectable} from '@angular/core'
import {User} from './model/user'
import {Subject} from 'rxjs'
import {Title} from '@angular/platform-browser'
import jwtDecode from "jwt-decode";
@Injectable({
providedIn: 'root'
})
export class AppState {
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'
authenticatedUser$ = new Subject<{ authenticated: boolean, authenticatedUser: User }>()
@ -20,15 +19,19 @@ export class AppState {
) {
}
authenticateUser(jwt: CreJwt) {
this.authenticatedUser = jwt.user
this.authenticationExpiration = jwt.exp
authenticateUser(user: User) {
this.authenticatedUser = user
this.isAuthenticated = true
}
authenticateGroupUser(user: User) {
this.authenticatedUser = user
this.isDefaultGroupUserAuthenticated = true
}
resetAuthenticatedUser() {
this.isAuthenticated = false
this.authenticationExpiration = -1
this.isDefaultGroupUserAuthenticated = false
this.authenticatedUser = null
}
@ -51,13 +54,16 @@ export class AppState {
})
}
get authenticationExpiration(): number {
return parseInt(sessionStorage.getItem(this.KEY_AUTHENTICATION_EXPIRATION))
get isDefaultGroupUserAuthenticated(): boolean {
return sessionStorage.getItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED) === 'true'
}
private set authenticationExpiration(value: number) {
console.error(value)
sessionStorage.setItem(this.KEY_AUTHENTICATION_EXPIRATION, value.toString())
private set isDefaultGroupUserAuthenticated(value: boolean) {
sessionStorage.setItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED, value.toString())
}
get hasCredentials(): boolean {
return this.isAuthenticated || this.isDefaultGroupUserAuthenticated
}
get authenticatedUser(): User {
@ -81,9 +87,3 @@ export class AppState {
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 {
this.accountService.logout(() => {
console.log('Successfully logged out')
})
this.subscribe(
this.accountService.logout(),
() => console.info('Successfully logged out')
)
super.ngOnDestroy()
}
@ -71,7 +72,7 @@ export class HeaderComponent extends SubscribingComponent {
set activeLink(link: string) {
this._activeLink = link
this.router.navigate([link])
this.urlUtils.navigateTo(link)
}
get activeLink() {

View File

@ -70,14 +70,13 @@ export class ApiService implements OnDestroy {
observe: 'response'
}
if (needAuthentication) {
if (this.checkAuthenticated()) {
if (this.appState.hasCredentials) {
if (httpOptions) {
httpOptions.withCredentials = true
} else {
console.error('httpOptions need to be specified to use credentials in HTTP methods.')
}
} else {
// this.appState.resetAuthenticatedUser()
this.navigateToLogin()
}
}
@ -89,12 +88,6 @@ export class ApiService implements OnDestroy {
.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() {
this.router.navigate(['/account/login'])
}

View File

@ -1,7 +1,7 @@
<cre-action-bar>
<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-bar>