diff --git a/src/lib/listing/page-header/page-header.component.html b/src/lib/listing/page-header/page-header.component.html
index 51f77af..942a874 100644
--- a/src/lib/listing/page-header/page-header.component.html
+++ b/src/lib/listing/page-header/page-header.component.html
@@ -12,11 +12,11 @@
-
+
diff --git a/src/lib/permissions/permissions.directive.ts b/src/lib/permissions/permissions.directive.ts
index b18fc22..f67aba3 100644
--- a/src/lib/permissions/permissions.directive.ts
+++ b/src/lib/permissions/permissions.directive.ts
@@ -1,4 +1,5 @@
import {
+ ChangeDetectorRef,
Directive,
EmbeddedViewRef,
EventEmitter,
@@ -18,8 +19,6 @@ import { IqserRolesService } from './services/roles.service';
import { IqserPermissionsService } from './services/permissions.service';
import { List } from '../utils';
-type NgTemplate = TemplateRef;
-
@Directive({
selector: '[allow]',
})
@@ -51,6 +50,7 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit {
private readonly _permissionsService: IqserPermissionsService,
private readonly _rolesService: IqserRolesService,
private readonly _viewContainer: ViewContainerRef,
+ private readonly _changeDetector: ChangeDetectorRef,
templateRef: TemplateRef,
) {
this.#thenTemplateRef = templateRef;
@@ -64,7 +64,7 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit {
}
@Input()
- set allowThen(template: NgTemplate) {
+ set allowThen(template: TemplateRef) {
assertTemplate('allowThen', template);
this.#thenTemplateRef = template;
this.#thenViewRef = false;
@@ -72,7 +72,7 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit {
}
@Input()
- set allowElse(template: NgTemplate) {
+ set allowElse(template: TemplateRef) {
assertTemplate('allowElse', template);
this.#elseTemplateRef = template;
this.#elseViewRef = false;
@@ -111,7 +111,8 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit {
this.permissionsUnauthorized.emit();
this.#thenViewRef = false;
- this.#elseViewRef = this.#showTemplate(this.#elseTemplateRef) ?? true;
+ this.#elseViewRef = this.#showTemplate(this.#elseTemplateRef);
+ this._changeDetector.markForCheck();
}
#showThenBlock() {
@@ -121,12 +122,13 @@ export class IqserPermissionsDirective implements OnDestroy, OnInit {
this.permissionsAuthorized.emit();
this.#elseViewRef = false;
- this.#thenViewRef = this.#showTemplate(this.#thenTemplateRef) ?? true;
+ this.#thenViewRef = this.#showTemplate(this.#thenTemplateRef);
+ this._changeDetector.markForCheck();
}
- #showTemplate(template?: NgTemplate) {
+ #showTemplate(template?: TemplateRef) {
this._viewContainer.clear();
- return template ? this._viewContainer.createEmbeddedView(template) : undefined;
+ return template ? this._viewContainer.createEmbeddedView(template) : true;
}
}
diff --git a/src/lib/permissions/services/permissions-guard.service.ts b/src/lib/permissions/services/permissions-guard.service.ts
index c8e9db3..9d8cf59 100644
--- a/src/lib/permissions/services/permissions-guard.service.ts
+++ b/src/lib/permissions/services/permissions-guard.service.ts
@@ -1,14 +1,5 @@
import { Injectable } from '@angular/core';
-import {
- ActivatedRouteSnapshot,
- CanActivate,
- CanActivateChild,
- CanLoad,
- NavigationExtras,
- Route,
- Router,
- RouterStateSnapshot,
-} from '@angular/router';
+import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, CanLoad, Route, Router, RouterStateSnapshot } from '@angular/router';
import { firstValueFrom, forkJoin, from, of } from 'rxjs';
import { first, mergeMap, tap } from 'rxjs/operators';
@@ -52,71 +43,53 @@ export class IqserPermissionsGuard implements CanActivate, CanLoad, CanActivateC
return this.#checkPermissions(route);
}
- #validate(permissions: IqserPermissionsData, route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) {
- if (isFunction(permissions.redirectTo) || !isRedirectWithParameters(permissions.redirectTo)) {
- return this.#checkRedirect(permissions, route, state);
- }
-
- return this.#validatePermissions(permissions, route, state);
- }
-
#checkPermissions(route: IqserActivatedRouteSnapshot | IqserRoute, state?: RouterStateSnapshot) {
const routePermissions = route.data?.permissions;
if (!routePermissions) {
- return Promise.resolve(true);
+ return true;
}
const permissions = transformPermission(routePermissions, route, state);
if (permissions.allow?.length > 0) {
- return this.#validate(permissions, route, state);
+ if (isFunction(permissions.redirectTo) || !isRedirectWithParameters(permissions.redirectTo)) {
+ return this.#checkRedirect(permissions, route, state);
+ }
+
+ return this.#validatePermissions(permissions, route, state);
}
- return Promise.resolve(true);
+ return true;
}
#redirectToAnotherRoute(
- permissionRedirectTo: RedirectTo | RedirectToFn,
+ redirectTo: RedirectTo | RedirectToFn,
route: ActivatedRouteSnapshot | Route,
failedPermissionName: string,
state?: RouterStateSnapshot,
) {
- const redirectTo = isFunction(permissionRedirectTo)
- ? permissionRedirectTo(failedPermissionName, route, state)
- : permissionRedirectTo;
+ const _redirectTo = isFunction(redirectTo) ? redirectTo(failedPermissionName, route, state) : redirectTo;
- if (isRedirectWithParameters(redirectTo)) {
- redirectTo.navigationCommands = this.#transformNavigationCommands(redirectTo.navigationCommands, route, state);
- redirectTo.navigationExtras = this.#transformNavigationExtras(redirectTo.navigationExtras ?? {}, route, state);
- return this._router.navigate(redirectTo.navigationCommands, redirectTo.navigationExtras);
+ if (isRedirectWithParameters(_redirectTo)) {
+ let navigationCommands = _redirectTo.navigationCommands;
+ navigationCommands = isFunction(navigationCommands)
+ ? navigationCommands(route, state)
+ : navigationCommands;
+ let navigationExtras = _redirectTo.navigationExtras ?? {};
+ navigationExtras = isFunction(navigationExtras) ? navigationExtras(route, state) : navigationExtras;
+ return this._router.navigate(navigationCommands, navigationExtras);
}
- if (Array.isArray(redirectTo)) {
- return this._router.navigate(redirectTo);
+ if (Array.isArray(_redirectTo)) {
+ return this._router.navigate(_redirectTo);
}
- return this._router.navigate([redirectTo]);
- }
-
- #transformNavigationCommands(
- navigationCommands: any[] | NavigationCommandsFn,
- route: ActivatedRouteSnapshot | Route,
- state?: RouterStateSnapshot,
- ) {
- return isFunction(navigationCommands) ? navigationCommands(route, state) : navigationCommands;
- }
-
- #transformNavigationExtras(
- navigationExtras: NavigationExtras | NavigationExtrasFn,
- route: ActivatedRouteSnapshot | Route,
- state?: RouterStateSnapshot,
- ): NavigationExtras {
- return isFunction(navigationExtras) ? navigationExtras(route, state) : navigationExtras;
+ return this._router.navigate([_redirectTo]);
}
#checkRedirect(permissions: IqserPermissionsData, route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) {
if (!permissions.allow || permissions.allow.length === 0) {
- return Promise.resolve(true);
+ return true;
}
let failedPermission = '';
@@ -205,27 +178,21 @@ export class IqserPermissionsGuard implements CanActivate, CanLoad, CanActivateC
}
#validatePermissions(
- purePermissions: IqserPermissionsData,
+ permissions: IqserPermissionsData,
route: ActivatedRouteSnapshot | Route,
state?: RouterStateSnapshot,
): Promise {
- const permissions: IqserPermissionsData = {
- ...purePermissions,
- };
+ const results = Promise.all([this._permissionsService.has(permissions.allow), this._rolesService.has(permissions.allow)]);
- return Promise.all([this._permissionsService.has(permissions.allow), this._rolesService.has(permissions.allow)]).then(
- ([hasPermission, hasRole]) => {
- if (hasPermission || hasRole) {
- return true;
- }
-
- if (permissions.redirectTo) {
+ return results
+ .then(([hasPermission, hasRole]) => hasPermission || hasRole)
+ .then(isAllowed => {
+ if (!isAllowed && permissions.redirectTo) {
const redirect = this.#redirectToAnotherRoute(permissions.redirectTo, route, permissions.allow[0], state);
return redirect.then(() => false);
}
- return false;
- },
- );
+ return isAllowed;
+ });
}
}
diff --git a/src/lib/permissions/services/permissions.service.ts b/src/lib/permissions/services/permissions.service.ts
index e0ba415..b2e3eb7 100644
--- a/src/lib/permissions/services/permissions.service.ts
+++ b/src/lib/permissions/services/permissions.service.ts
@@ -1,10 +1,11 @@
import { Injectable } from '@angular/core';
-import { BehaviorSubject, Observable } from 'rxjs';
+import { BehaviorSubject, firstValueFrom, Observable, of, switchMap } from 'rxjs';
import { isArray, isString, toArray } from '../utils';
import { IqserPermissions, PermissionValidationFn } from '../types';
import { List } from '../../utils';
+import { map } from 'rxjs/operators';
@Injectable()
export class IqserPermissionsService {
@@ -22,9 +23,8 @@ export class IqserPermissionsService {
has(permission: string): Promise;
has(permissions: List): Promise;
has(permissions: string | List): Promise;
- has(value: string | List): Promise {
- const isEmpty = !value || value.length === 0;
- return isEmpty ? Promise.resolve(true) : this.#has(toArray(value));
+ has(permissions: string | List): Promise {
+ return firstValueFrom(this.has$(permissions));
}
load(permissions: IqserPermissions | List) {
@@ -43,11 +43,29 @@ export class IqserPermissionsService {
}
get(): IqserPermissions;
+
get(permission: string): PermissionValidationFn | undefined;
+
get(permission?: string): IqserPermissions | PermissionValidationFn | undefined {
return permission ? this.#permissions$.value[permission] : this.#permissions$.value;
}
+ has$(permission: string): Observable;
+ has$(permissions: List): Observable;
+ has$(permissions: string | List): Observable;
+ has$(permissions: string | List) {
+ const isEmpty = !permissions || permissions.length === 0;
+ if (isEmpty) {
+ return of(true);
+ }
+
+ return this.permissions$.pipe(
+ map(all => toArray(permissions).map(permission => all[permission]?.(permission, all) ?? false)),
+ switchMap(promises => Promise.all(promises)),
+ map(results => results.every(result => result)),
+ );
+ }
+
#reduce(permissions: IqserPermissions | List, initialValue = {} as IqserPermissions) {
if (isArray(permissions)) {
return this.#permissions$.next(this.#reduceList(permissions, initialValue));
@@ -67,13 +85,4 @@ export class IqserPermissionsService {
return { ...acc, [permission]: () => true };
}, initialValue);
}
-
- #has(permissions: List): Promise {
- const promises = permissions.map(permission => {
- const validationFn = this.#permissions$.value[permission];
- return validationFn?.(permission, this.#permissions$.value) ?? false;
- });
-
- return Promise.all(promises).then(results => results.every(result => result));
- }
}
diff --git a/src/lib/users/services/iqser-user.service.ts b/src/lib/users/services/iqser-user.service.ts
index 44640b5..befbf72 100644
--- a/src/lib/users/services/iqser-user.service.ts
+++ b/src/lib/users/services/iqser-user.service.ts
@@ -14,6 +14,12 @@ import { IMyProfileUpdateRequest } from '../types/my-profile-update.request';
import { IProfileUpdateRequest } from '../types/profile-update.request';
import { KeycloakProfile } from 'keycloak-js';
import { IqserUser } from '../iqser-user.model';
+import { IqserPermissionsService, IqserRolesService } from '../../permissions';
+
+interface IToken {
+ sub: string;
+ resource_access: { account: { roles: List }; redaction: { roles: List } };
+}
@Injectable()
export abstract class IqserUserService<
@@ -28,6 +34,8 @@ export abstract class IqserUserService<
protected readonly _baseHref = inject(BASE_HREF);
protected readonly _keycloakService = inject(KeycloakService);
protected readonly _cacheApiService = inject(CacheApiService);
+ protected readonly _permissionsService = inject(IqserPermissionsService);
+ protected readonly _rolesService = inject(IqserRolesService);
constructor() {
super();
@@ -65,11 +73,6 @@ export abstract class IqserUserService<
}
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 => this._rolesFilter(role));
let profile;
try {
profile = await this._keycloakService.loadUserProfile(true);
@@ -79,6 +82,13 @@ export abstract class IqserUserService<
return;
}
+ const token = await this._keycloakService.getToken();
+ const decoded: IToken = jwt_decode(token);
+ const userId = decoded.sub;
+ this._permissionsService.load(decoded.resource_access.redaction.roles);
+
+ const roles = this._keycloakService.getUserRoles(true).filter(role => this._rolesFilter(role));
+ this._rolesService.load(roles);
const user = new this._entityClass(profile, roles, userId);
this.replace(user);