From e007d5fe57d15ba74a5a8313e5c9124a34e916b6 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Wed, 27 Jul 2022 10:35:06 +0300 Subject: [PATCH] update auth & common ui modules --- src/lib/auth/auth.module.ts | 12 ++++++--- src/lib/auth/base-user.service.ts | 30 +++++++++++++++++------ src/lib/auth/default-user.service.ts | 1 + src/lib/auth/role.guard.ts | 14 +++++------ src/lib/auth/types/auth-module-options.ts | 9 ++++++- src/lib/common-ui.module.ts | 29 ++++++++++++++++------ src/lib/services/base-config.service.ts | 2 +- src/lib/utils/types/common-ui-options.ts | 12 ++++++--- 8 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/lib/auth/auth.module.ts b/src/lib/auth/auth.module.ts index 2bc42f9..0639904 100644 --- a/src/lib/auth/auth.module.ts +++ b/src/lib/auth/auth.module.ts @@ -63,14 +63,18 @@ export function keycloakInitializer( ], }) export class AuthModule extends ModuleWithOptions { - static forRoot>( - options: AuthModuleOptions, - ): ModuleWithProviders { + static forRoot< + Interface extends IIqserUser, + Class extends IqserUser & Interface, + UserService extends BaseUserService, + RolesGuard extends RoleGuard = RoleGuard, + >(options: AuthModuleOptions): ModuleWithProviders { const userService = this._getService(BaseUserService, DefaultUserService, options.existingUserService); + const roleGuard = this._getService(RoleGuard, RoleGuard, options.existingRoleGuard); return { ngModule: AuthModule, - providers: [userService], + providers: [userService, roleGuard], }; } } diff --git a/src/lib/auth/base-user.service.ts b/src/lib/auth/base-user.service.ts index 4a3d806..7a3e019 100644 --- a/src/lib/auth/base-user.service.ts +++ b/src/lib/auth/base-user.service.ts @@ -22,6 +22,7 @@ export abstract class BaseUserService< > extends EntitiesService { readonly currentUser$: Observable; protected abstract readonly _defaultModelPath: string; + protected abstract readonly _rolesFilter: (role: string) => boolean; protected abstract readonly _entityClass: new (entityInterface: Interface | KeycloakProfile, ...args: unknown[]) => Class; protected readonly _currentUser$ = new BehaviorSubject(undefined); protected readonly _baseHref = inject(BASE_HREF); @@ -60,13 +61,22 @@ export abstract class BaseUserService< ); } - async loadCurrentUser(): Promise { + async loadCurrentUser(): Promise { const token = await this._keycloakService.getToken(); const decoded = jwt_decode(token); const userId = (<{ sub: string }>decoded).sub; - const roles = this._keycloakService.getUserRoles(true).filter(role => role.startsWith('RED_')); - const user = new this._entityClass(await this._keycloakService.loadUserProfile(true), roles, userId); + const roles = this._keycloakService.getUserRoles(true).filter(role => this._rolesFilter(role)); + let profile; + try { + profile = await this._keycloakService.loadUserProfile(true); + } catch (e) { + await this._keycloakService.logout(); + console.log(e); + return; + } + + const user = new this._entityClass(profile, roles, userId); this.replace(user); this._currentUser$.next(this.find(userId)); @@ -78,8 +88,8 @@ export abstract class BaseUserService< return this.find(userId)?.name; } - getAll(): Observable { - return super.getAll(this._defaultModelPath, [{ key: 'refreshCache', value: true }]); + getAll(url = this._defaultModelPath): Observable { + return super.getAll(url, [{ key: 'refreshCache', value: true }]); } @Validate() @@ -112,10 +122,14 @@ export abstract class BaseUserService< return new this._entityClass({ username: 'System' }, [], 'system'); } - return super.find(id) || new this._entityClass({ username: 'Deleted User' }, [], 'deleted'); + if (!id) { + return undefined; + } + + return super.find(id) ?? new this._entityClass({ username: 'Deleted User' }, [], 'deleted'); } } -export function getCurrentUser() { - return inject(BaseUserService).currentUser; +export function getCurrentUser() { + return inject>(BaseUserService).currentUser; } diff --git a/src/lib/auth/default-user.service.ts b/src/lib/auth/default-user.service.ts index 4b0025a..54dbf0d 100644 --- a/src/lib/auth/default-user.service.ts +++ b/src/lib/auth/default-user.service.ts @@ -6,4 +6,5 @@ import { BaseUserService } from './base-user.service'; export class DefaultUserService extends BaseUserService { protected readonly _defaultModelPath = 'user'; protected readonly _entityClass = IqserUser; + protected readonly _rolesFilter = () => true; } diff --git a/src/lib/auth/role.guard.ts b/src/lib/auth/role.guard.ts index 90ba6f3..86afd68 100644 --- a/src/lib/auth/role.guard.ts +++ b/src/lib/auth/role.guard.ts @@ -1,17 +1,15 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; +import { inject, Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { LoadingService } from '../loading'; import { BaseUserService } from './base-user.service'; @Injectable() export class RoleGuard implements CanActivate { - constructor( - private readonly _router: Router, - private readonly _loadingService: LoadingService, - private readonly _userService: BaseUserService, - ) {} + protected readonly _router = inject(Router); + protected readonly _loadingService = inject(LoadingService); + protected readonly _userService = inject(BaseUserService); - async canActivate(route: ActivatedRouteSnapshot): Promise { + async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { const currentUser = this._userService.currentUser; if (!currentUser || !currentUser.hasAnyRole) { await this._router.navigate(['/auth-error']); diff --git a/src/lib/auth/types/auth-module-options.ts b/src/lib/auth/types/auth-module-options.ts index 60ae6e3..05bdc0c 100644 --- a/src/lib/auth/types/auth-module-options.ts +++ b/src/lib/auth/types/auth-module-options.ts @@ -2,7 +2,14 @@ import { BaseUserService } from '../base-user.service'; import { IIqserUser } from './user.response'; import { IqserUser } from '../user.model'; import { Type } from '@angular/core'; +import { RoleGuard } from '../role.guard'; -export interface AuthModuleOptions = BaseUserService> { +export interface AuthModuleOptions< + I extends IIqserUser = IIqserUser, + C extends IqserUser & I = IqserUser & I, + T extends BaseUserService = BaseUserService, + R extends RoleGuard = RoleGuard, +> { existingUserService?: Type; + existingRoleGuard?: Type; } diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index d2c177f..5e0e794 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -4,7 +4,7 @@ import { MatIconModule } from '@angular/material/icon'; import { TranslateModule } from '@ngx-translate/core'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { SortByPipe } from './sorting'; -import { CapitalizePipe, HumanizePipe } from './utils'; +import { BaseAppConfig, CapitalizePipe, HumanizePipe } from './utils'; import { HiddenActionComponent, LogoComponent, @@ -27,7 +27,6 @@ import { IqserEmptyStatesModule } from './empty-states'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; -import { KeycloakAngularModule } from 'keycloak-angular'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { UploadFileComponent } from './upload-file/upload-file.component'; import { DragDropFileUploadDirective } from './upload-file/drag-drop-file-upload.directive'; @@ -40,7 +39,15 @@ import { BaseConfigService } from './services/base-config.service'; import { DefaultUserPreferenceService } from './services/default-user-preference.service'; import { ModuleWithOptions } from './utils/module-with-options'; -const matModules = [MatIconModule, MatProgressSpinnerModule, MatButtonModule, MatDialogModule, MatCheckboxModule, MatTooltipModule]; +const matModules = [ + MatIconModule, + MatProgressSpinnerModule, + MatButtonModule, + MatDialogModule, + MatCheckboxModule, + MatTooltipModule, + MatProgressBarModule, +]; const modules = [ TranslateModule, IqserIconsModule, @@ -73,13 +80,15 @@ const pipes = [SortByPipe, HumanizePipe, CapitalizePipe]; @NgModule({ declarations: [...components, ...pipes], - imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule, KeycloakAngularModule, MatProgressBarModule], + imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule], exports: [...components, ...pipes, ...modules], }) export class CommonUiModule extends ModuleWithOptions { - static forRoot( - options: CommonUiOptions, - ): ModuleWithProviders { + static forRoot< + UserPreference extends BaseUserPreferenceService, + Config extends BaseConfigService, + AppConfig extends BaseAppConfig = BaseAppConfig, + >(options: CommonUiOptions): ModuleWithProviders { const userPreferenceService = this._getService( BaseUserPreferenceService, DefaultUserPreferenceService, @@ -91,9 +100,13 @@ export class CommonUiModule extends ModuleWithOptions { providers: [ userPreferenceService, { - provide: BaseConfigService, + provide: options.configService, useFactory: options.configServiceFactory, }, + { + provide: BaseConfigService, + useExisting: options.configService, + }, ], }; } diff --git a/src/lib/services/base-config.service.ts b/src/lib/services/base-config.service.ts index 57c62cd..49d3462 100644 --- a/src/lib/services/base-config.service.ts +++ b/src/lib/services/base-config.service.ts @@ -20,7 +20,7 @@ export class BaseConfigService { this._titleService.setTitle(this._values.APP_NAME); } - private _checkFrontendVersion(): void { + protected _checkFrontendVersion(): void { this._cacheApiService.getCachedValue('FRONTEND_APP_VERSION').then(async lastVersion => { const version = this._values.FRONTEND_APP_VERSION; console.log('Last app version: ', lastVersion, ' current version ', version); diff --git a/src/lib/utils/types/common-ui-options.ts b/src/lib/utils/types/common-ui-options.ts index e781a65..5289e4a 100644 --- a/src/lib/utils/types/common-ui-options.ts +++ b/src/lib/utils/types/common-ui-options.ts @@ -1,7 +1,13 @@ import { BaseConfigService, BaseUserPreferenceService } from '../../services'; import { Type } from '@angular/core'; +import { BaseAppConfig } from '../base-app-config'; -export interface CommonUiOptions { - existingUserPreferenceService?: Type; - configServiceFactory: () => C; +export interface CommonUiOptions< + UserPreference extends BaseUserPreferenceService, + Config extends BaseConfigService, + AppConfig extends BaseAppConfig, +> { + existingUserPreferenceService?: Type; + configService: Type; + configServiceFactory: () => Config; }