From 8fde9eec377685c3148ff39236f3ac74318cfc6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Agneray?= <francois.agneray@lam.fr>
Date: Thu, 19 Nov 2020 16:53:44 +0100
Subject: [PATCH] Update auth module

---
 package.json                               |  1 +
 src/app/auth/auth.action.ts                | 19 +++++++++++++++++-
 src/app/auth/auth.effects.ts               | 23 ++++++++++++++++++----
 src/app/auth/auth.reducer.ts               | 13 +++++++++++-
 src/app/auth/auth.selector.ts              | 11 ++++++++---
 src/app/auth/auth.service.ts               |  7 ++++++-
 src/app/core/components/nav.component.html |  8 ++++----
 src/app/core/components/nav.component.ts   |  1 +
 src/environments/environment.prod.ts       |  1 +
 src/environments/environment.ts            |  1 +
 yarn.lock                                  |  5 +++++
 11 files changed, 76 insertions(+), 14 deletions(-)

diff --git a/package.json b/package.json
index c09cf705..063d8be8 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
     "angular-auth-oidc-client": "^11.2.2",
     "bootstrap": "^4.5.3",
     "d3": "^5.15.1",
+    "jwt-decode": "^3.1.2",
     "ng-inline-svg": "^11.0.1",
     "ngx-bootstrap": "^6.1.0",
     "ngx-json-viewer": "^2.4.0",
diff --git a/src/app/auth/auth.action.ts b/src/app/auth/auth.action.ts
index 84c0990f..66b97e09 100644
--- a/src/app/auth/auth.action.ts
+++ b/src/app/auth/auth.action.ts
@@ -15,6 +15,9 @@ export const LOGIN = '[Auth] Login';
 export const LOGIN_COMPLETE = '[Auth] Login Complete';
 export const LOGOUT = '[Auth] Logout';
 export const OPEN_EDIT_PROFILE = '[Auth] Edit Profile';
+export const GET_USER_ROLES = '[Auth] Get User Roles';
+export const PARSE_JWT = '[Auth] Parse JWT';
+export const LOAD_USER_ROLES = '[Auth] Load User Roles';
 
 export class CheckAuthAction implements Action {
     readonly type = CHECK_AUTH;
@@ -63,10 +66,24 @@ export class OpenEditProfileAction implements Action {
     constructor(public payload: {} = null) { }
 }
 
+export class ParseJwtAction implements Action {
+    readonly type = PARSE_JWT;
+
+    constructor(public payload: string) { }
+}
+
+export class LoadUserRolesAction implements Action {
+    readonly type = LOAD_USER_ROLES;
+
+    constructor(public payload: string[]) { }
+}
+
 export type Actions
     = CheckAuthAction
     | CheckAuthCompleteAction
     | LoginAction
     | LoginCompleteAction
     | LogoutAction
-    | OpenEditProfileAction;
+    | OpenEditProfileAction
+    | ParseJwtAction
+    | LoadUserRolesAction;
diff --git a/src/app/auth/auth.effects.ts b/src/app/auth/auth.effects.ts
index f3418fd1..f8472960 100644
--- a/src/app/auth/auth.effects.ts
+++ b/src/app/auth/auth.effects.ts
@@ -13,6 +13,7 @@ import { Router } from '@angular/router';
 import { of } from 'rxjs';
 import { switchMap, map, tap } from 'rxjs/operators';
 import { Effect, Actions, ofType } from '@ngrx/effects';
+import jwt_decode from 'jwt-decode';
 
 import * as authActions from './auth.action';
 import { AuthService } from './auth.service';
@@ -39,7 +40,7 @@ export class AuthEffects {
         tap((action: authActions.LoginAction) => {
             const redirectUrl = action.payload;
             if (redirectUrl !== null) {
-                sessionStorage.setItem(environment.ssoClientId + '_redirectUrl', this.router.url);
+                sessionStorage.setItem(environment.ssoClientId + '_redirectUrl', redirectUrl);
             }
             return this.authService.doLogin()
         })
@@ -65,9 +66,10 @@ export class AuthEffects {
             
             if (isAuthenticated) {
                 return this.authService.userData.pipe(
-                    map((profile) =>
-                        new authActions.LoginCompleteAction(profile)
-                    )
+                    switchMap((profile) => [
+                        new authActions.LoginCompleteAction(profile),
+                        new authActions.ParseJwtAction(this.authService.token)
+                    ])
                 );
             } else {
                 return of({ type: '[No Action] ' + authActions.CHECK_AUTH_COMPLETE });
@@ -87,6 +89,19 @@ export class AuthEffects {
         })
     );
 
+    @Effect()
+    parseJWTAction$ = this.actions$.pipe(
+        ofType(authActions.PARSE_JWT),
+        switchMap((action: authActions.ParseJwtAction) => {
+            const jwt = jwt_decode(action.payload) as any;
+            if (environment.ssoName === 'auth0') {
+                return of({ type: '[No Action] ' + authActions.PARSE_JWT });
+            } else {
+                return of(new authActions.LoadUserRolesAction(jwt.realm_access.roles));
+            }
+        })
+    );
+
     @Effect({ dispatch: false })
     logoutAction$ = this.actions$.pipe(
         ofType(authActions.LOGOUT),
diff --git a/src/app/auth/auth.reducer.ts b/src/app/auth/auth.reducer.ts
index b50c5516..6f46c3e1 100644
--- a/src/app/auth/auth.reducer.ts
+++ b/src/app/auth/auth.reducer.ts
@@ -17,11 +17,13 @@ import * as actions from './auth.action';
 export interface State {
     isAuthenticated: boolean;
     userProfile: any;
+    userRoles: string[];
 }
 
 export const initialState: State = {
     isAuthenticated: false,
-    userProfile: null
+    userProfile: null,
+    userRoles: []
 };
 
 /**
@@ -43,6 +45,14 @@ export function reducer(state: State = initialState, action: actions.Actions): S
                 userProfile
             };
 
+        case actions.LOAD_USER_ROLES:
+            const userRoles = action.payload;
+
+            return {
+                ...state,
+                userRoles
+            };    
+
         case actions.LOGOUT:
             return {
                 ...state,
@@ -57,3 +67,4 @@ export function reducer(state: State = initialState, action: actions.Actions): S
 
 export const isAuthenticated = (state: State) => state.isAuthenticated;
 export const getUserProfile = (state: State) => state.userProfile;
+export const getUserRoles = (state: State) => state.userRoles;
diff --git a/src/app/auth/auth.selector.ts b/src/app/auth/auth.selector.ts
index c312a6e4..db6ccdde 100644
--- a/src/app/auth/auth.selector.ts
+++ b/src/app/auth/auth.selector.ts
@@ -11,14 +11,19 @@ import { createSelector, createFeatureSelector } from '@ngrx/store';
 
 import * as auth from './auth.reducer';
 
-export const getAuthtate = createFeatureSelector<auth.State>('auth');
+export const getAuthState = createFeatureSelector<auth.State>('auth');
 
 export const isAuthenticated = createSelector(
-    getAuthtate,
+    getAuthState,
     auth.isAuthenticated
 );
 
 export const getUserProfile = createSelector(
-    getAuthtate,
+    getAuthState,
     auth.getUserProfile
 );
+
+export const getUserRoles = createSelector(
+    getAuthState,
+    auth.getUserRoles
+);
diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts
index 19481135..5b82d73e 100644
--- a/src/app/auth/auth.service.ts
+++ b/src/app/auth/auth.service.ts
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
 
 import { of } from 'rxjs';
 import { OidcSecurityService } from 'angular-auth-oidc-client';
+import { environment } from 'src/environments/environment';
 
 @Injectable({ providedIn: 'root' })
 export class AuthService {
@@ -12,7 +13,11 @@ export class AuthService {
     }
 
     get token() {
-        return this.oidcSecurityService.getIdToken();
+        if (environment.ssoName === 'auth0') {
+            return this.oidcSecurityService.getIdToken();
+        } else {
+            return this.oidcSecurityService.getToken();
+        }
     }
 
     get userData() {
diff --git a/src/app/core/components/nav.component.html b/src/app/core/components/nav.component.html
index 8161ec80..5d0a999d 100644
--- a/src/app/core/components/nav.component.html
+++ b/src/app/core/components/nav.component.html
@@ -49,12 +49,12 @@
                     <span class="dropdown-item font-italic">{{ userProfile.email }}</span>
                 </li>
                 <li class="divider dropdown-divider"></li>
-                <li role="menuitem">
+                <li *ngIf="ssoName != 'auth0'" role="menuitem">
                     <button class="dropdown-item pointer" (click)="openEditProfile.emit()">
                         <span class="fas fa-id-card"></span> Edit profile
                     </button>
                 </li>
-                <li class="divider dropdown-divider"></li>
+                <li *ngIf="ssoName != 'auth0'" class="divider dropdown-divider"></li>
                 <li role="menuitem">
                     <button class="dropdown-item text-danger pointer" (click)="logout.emit()">
                         <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out
@@ -96,12 +96,12 @@
                 </a>
             </li>
             <li *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" class="divider dropdown-divider"></li>
-            <li *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" role="menuitem">
+            <li *ngIf="getConfig('authentication', 'allowed') && isAuthenticated && ssoName != 'auth0'" role="menuitem">
                 <button class="dropdown-item pointer" (click)="openEditProfile.emit()">
                     <span class="fas fa-id-card"></span> Edit profile
                 </button>
             </li>
-            <li *ngIf="getConfig('authentication', 'allowed')" class="divider dropdown-divider"></li>
+            <li *ngIf="getConfig('authentication', 'allowed') && ssoName != 'auth0'" class="divider dropdown-divider"></li>
             <li role="menuitem">
                 <button *ngIf="getConfig('authentication', 'allowed') && !isAuthenticated"
                     class="dropdown-item text-success"
diff --git a/src/app/core/components/nav.component.ts b/src/app/core/components/nav.component.ts
index fce80ae4..e65122ab 100644
--- a/src/app/core/components/nav.component.ts
+++ b/src/app/core/components/nav.component.ts
@@ -31,6 +31,7 @@ export class NavComponent {
     @Output() openEditProfile: EventEmitter<any> = new EventEmitter();
 
     baseHref: string = environment.baseHref;
+    ssoName: string = environment.ssoName;
 
     /**
      * Checks if given key for the given configuration propriety is allowed.
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
index 777ce7a7..bcf02505 100644
--- a/src/environments/environment.prod.ts
+++ b/src/environments/environment.prod.ts
@@ -14,6 +14,7 @@ export const environment = {
     spectraUrl: 'https://cesam.lam.fr/anis-tools/spectra_to_csv/?file=/dataproject/SPECTRA/',
     instanceName: 'default',
     baseHref: '/',
+    ssoName: 'keycloak',
     ssoAuthUrl: 'https://anis-dev.lam.fr/auth/realms/anis',
     ssoClientId: 'anis-client'
 };
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 91489b09..a92b10b3 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -18,6 +18,7 @@ export const environment = {
     spectraUrl: 'https://cesam.lam.fr/anis-tools/spectra_to_csv/?file=/dataproject/SPECTRA/',
     instanceName: 'default',
     baseHref: '/',
+    ssoName: 'keycloak',
     ssoAuthUrl: 'http://localhost:8180/auth/realms/anis',
     ssoClientId: 'anis-client'
 };
diff --git a/yarn.lock b/yarn.lock
index 4dddb419..9d7951e3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5962,6 +5962,11 @@ jszip@^3.1.3:
     readable-stream "~2.3.6"
     set-immediate-shim "~1.0.1"
 
+jwt-decode@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
+  integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
+
 karma-chrome-launcher@~3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738"
-- 
GitLab