update auth & common ui modules

This commit is contained in:
Dan Percic 2022-07-27 10:35:06 +03:00
parent 0a238622e7
commit e007d5fe57
8 changed files with 76 additions and 33 deletions

View File

@ -63,14 +63,18 @@ export function keycloakInitializer(
], ],
}) })
export class AuthModule extends ModuleWithOptions { export class AuthModule extends ModuleWithOptions {
static forRoot<I extends IIqserUser, C extends IqserUser & I, S extends BaseUserService<I, C>>( static forRoot<
options: AuthModuleOptions<I, C, S>, Interface extends IIqserUser,
): ModuleWithProviders<AuthModule> { Class extends IqserUser & Interface,
UserService extends BaseUserService<Interface, Class>,
RolesGuard extends RoleGuard = RoleGuard,
>(options: AuthModuleOptions<Interface, Class, UserService, RolesGuard>): ModuleWithProviders<AuthModule> {
const userService = this._getService(BaseUserService, DefaultUserService, options.existingUserService); const userService = this._getService(BaseUserService, DefaultUserService, options.existingUserService);
const roleGuard = this._getService(RoleGuard, RoleGuard, options.existingRoleGuard);
return { return {
ngModule: AuthModule, ngModule: AuthModule,
providers: [userService], providers: [userService, roleGuard],
}; };
} }
} }

View File

@ -22,6 +22,7 @@ export abstract class BaseUserService<
> extends EntitiesService<Interface, Class> { > extends EntitiesService<Interface, Class> {
readonly currentUser$: Observable<Class | undefined>; readonly currentUser$: Observable<Class | undefined>;
protected abstract readonly _defaultModelPath: string; protected abstract readonly _defaultModelPath: string;
protected abstract readonly _rolesFilter: (role: string) => boolean;
protected abstract readonly _entityClass: new (entityInterface: Interface | KeycloakProfile, ...args: unknown[]) => Class; protected abstract readonly _entityClass: new (entityInterface: Interface | KeycloakProfile, ...args: unknown[]) => Class;
protected readonly _currentUser$ = new BehaviorSubject<Class | undefined>(undefined); protected readonly _currentUser$ = new BehaviorSubject<Class | undefined>(undefined);
protected readonly _baseHref = inject(BASE_HREF); protected readonly _baseHref = inject(BASE_HREF);
@ -60,13 +61,22 @@ export abstract class BaseUserService<
); );
} }
async loadCurrentUser(): Promise<Class> { async loadCurrentUser(): Promise<Class | undefined> {
const token = await this._keycloakService.getToken(); const token = await this._keycloakService.getToken();
const decoded = jwt_decode(token); const decoded = jwt_decode(token);
const userId = (<{ sub: string }>decoded).sub; const userId = (<{ sub: string }>decoded).sub;
const roles = this._keycloakService.getUserRoles(true).filter(role => role.startsWith('RED_')); const roles = this._keycloakService.getUserRoles(true).filter(role => this._rolesFilter(role));
const user = new this._entityClass(await this._keycloakService.loadUserProfile(true), roles, userId); 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.replace(user);
this._currentUser$.next(this.find(userId)); this._currentUser$.next(this.find(userId));
@ -78,8 +88,8 @@ export abstract class BaseUserService<
return this.find(userId)?.name; return this.find(userId)?.name;
} }
getAll(): Observable<Interface[]> { getAll(url = this._defaultModelPath): Observable<Interface[]> {
return super.getAll(this._defaultModelPath, [{ key: 'refreshCache', value: true }]); return super.getAll(url, [{ key: 'refreshCache', value: true }]);
} }
@Validate() @Validate()
@ -112,10 +122,14 @@ export abstract class BaseUserService<
return new this._entityClass({ username: 'System' }, [], 'system'); 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() { export function getCurrentUser<Class extends IqserUser = IqserUser>() {
return inject(BaseUserService).currentUser; return inject<BaseUserService<IIqserUser, Class>>(BaseUserService).currentUser;
} }

View File

@ -6,4 +6,5 @@ import { BaseUserService } from './base-user.service';
export class DefaultUserService extends BaseUserService { export class DefaultUserService extends BaseUserService {
protected readonly _defaultModelPath = 'user'; protected readonly _defaultModelPath = 'user';
protected readonly _entityClass = IqserUser; protected readonly _entityClass = IqserUser;
protected readonly _rolesFilter = () => true;
} }

View File

@ -1,17 +1,15 @@
import { Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { LoadingService } from '../loading'; import { LoadingService } from '../loading';
import { BaseUserService } from './base-user.service'; import { BaseUserService } from './base-user.service';
@Injectable() @Injectable()
export class RoleGuard implements CanActivate { export class RoleGuard implements CanActivate {
constructor( protected readonly _router = inject(Router);
private readonly _router: Router, protected readonly _loadingService = inject(LoadingService);
private readonly _loadingService: LoadingService, protected readonly _userService = inject(BaseUserService);
private readonly _userService: BaseUserService,
) {}
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
const currentUser = this._userService.currentUser; const currentUser = this._userService.currentUser;
if (!currentUser || !currentUser.hasAnyRole) { if (!currentUser || !currentUser.hasAnyRole) {
await this._router.navigate(['/auth-error']); await this._router.navigate(['/auth-error']);

View File

@ -2,7 +2,14 @@ import { BaseUserService } from '../base-user.service';
import { IIqserUser } from './user.response'; import { IIqserUser } from './user.response';
import { IqserUser } from '../user.model'; import { IqserUser } from '../user.model';
import { Type } from '@angular/core'; import { Type } from '@angular/core';
import { RoleGuard } from '../role.guard';
export interface AuthModuleOptions<I extends IIqserUser, C extends IqserUser & I, T extends BaseUserService<I, C> = BaseUserService<I, C>> { export interface AuthModuleOptions<
I extends IIqserUser = IIqserUser,
C extends IqserUser & I = IqserUser & I,
T extends BaseUserService<I, C> = BaseUserService<I, C>,
R extends RoleGuard = RoleGuard,
> {
existingUserService?: Type<T>; existingUserService?: Type<T>;
existingRoleGuard?: Type<R>;
} }

View File

@ -4,7 +4,7 @@ import { MatIconModule } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { SortByPipe } from './sorting'; import { SortByPipe } from './sorting';
import { CapitalizePipe, HumanizePipe } from './utils'; import { BaseAppConfig, CapitalizePipe, HumanizePipe } from './utils';
import { import {
HiddenActionComponent, HiddenActionComponent,
LogoComponent, LogoComponent,
@ -27,7 +27,6 @@ import { IqserEmptyStatesModule } from './empty-states';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { KeycloakAngularModule } from 'keycloak-angular';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { UploadFileComponent } from './upload-file/upload-file.component'; import { UploadFileComponent } from './upload-file/upload-file.component';
import { DragDropFileUploadDirective } from './upload-file/drag-drop-file-upload.directive'; 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 { DefaultUserPreferenceService } from './services/default-user-preference.service';
import { ModuleWithOptions } from './utils/module-with-options'; 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 = [ const modules = [
TranslateModule, TranslateModule,
IqserIconsModule, IqserIconsModule,
@ -73,13 +80,15 @@ const pipes = [SortByPipe, HumanizePipe, CapitalizePipe];
@NgModule({ @NgModule({
declarations: [...components, ...pipes], declarations: [...components, ...pipes],
imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule, KeycloakAngularModule, MatProgressBarModule], imports: [CommonModule, ...matModules, ...modules, FormsModule, ReactiveFormsModule],
exports: [...components, ...pipes, ...modules], exports: [...components, ...pipes, ...modules],
}) })
export class CommonUiModule extends ModuleWithOptions { export class CommonUiModule extends ModuleWithOptions {
static forRoot<T extends BaseUserPreferenceService, C extends BaseConfigService>( static forRoot<
options: CommonUiOptions<T, C>, UserPreference extends BaseUserPreferenceService,
): ModuleWithProviders<CommonUiModule> { Config extends BaseConfigService<AppConfig>,
AppConfig extends BaseAppConfig = BaseAppConfig,
>(options: CommonUiOptions<UserPreference, Config, AppConfig>): ModuleWithProviders<CommonUiModule> {
const userPreferenceService = this._getService( const userPreferenceService = this._getService(
BaseUserPreferenceService, BaseUserPreferenceService,
DefaultUserPreferenceService, DefaultUserPreferenceService,
@ -91,9 +100,13 @@ export class CommonUiModule extends ModuleWithOptions {
providers: [ providers: [
userPreferenceService, userPreferenceService,
{ {
provide: BaseConfigService, provide: options.configService,
useFactory: options.configServiceFactory, useFactory: options.configServiceFactory,
}, },
{
provide: BaseConfigService,
useExisting: options.configService,
},
], ],
}; };
} }

View File

@ -20,7 +20,7 @@ export class BaseConfigService<T extends BaseAppConfig = BaseAppConfig> {
this._titleService.setTitle(this._values.APP_NAME); this._titleService.setTitle(this._values.APP_NAME);
} }
private _checkFrontendVersion(): void { protected _checkFrontendVersion(): void {
this._cacheApiService.getCachedValue('FRONTEND_APP_VERSION').then(async lastVersion => { this._cacheApiService.getCachedValue('FRONTEND_APP_VERSION').then(async lastVersion => {
const version = this._values.FRONTEND_APP_VERSION; const version = this._values.FRONTEND_APP_VERSION;
console.log('Last app version: ', lastVersion, ' current version ', version); console.log('Last app version: ', lastVersion, ' current version ', version);

View File

@ -1,7 +1,13 @@
import { BaseConfigService, BaseUserPreferenceService } from '../../services'; import { BaseConfigService, BaseUserPreferenceService } from '../../services';
import { Type } from '@angular/core'; import { Type } from '@angular/core';
import { BaseAppConfig } from '../base-app-config';
export interface CommonUiOptions<T extends BaseUserPreferenceService, C extends BaseConfigService> { export interface CommonUiOptions<
existingUserPreferenceService?: Type<T>; UserPreference extends BaseUserPreferenceService,
configServiceFactory: () => C; Config extends BaseConfigService<AppConfig>,
AppConfig extends BaseAppConfig,
> {
existingUserPreferenceService?: Type<UserPreference>;
configService: Type<Config>;
configServiceFactory: () => Config;
} }