RED-6713 remove tenants context holder

This commit is contained in:
Dan Percic 2023-05-18 12:19:53 +03:00
parent dbfde290c8
commit d0551742ec
9 changed files with 43 additions and 50 deletions

View File

@ -17,7 +17,7 @@ import { IqserPermissionsService } from './permissions.service';
import { IqserRolesService } from './roles.service'; import { IqserRolesService } from './roles.service';
import { isArray, isFunction, isRedirectWithParameters, isString, transformPermission } from '../utils'; import { isArray, isFunction, isRedirectWithParameters, isString, transformPermission } from '../utils';
import { List } from '../../utils'; import { List } from '../../utils';
import { TenantContextHolder } from '../../tenants'; import { TenantsService } from '../../tenants';
export interface IqserPermissionsData { export interface IqserPermissionsData {
readonly allow: string | List; readonly allow: string | List;
@ -30,7 +30,7 @@ export interface IqserPermissionsData {
export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivateChild { export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivateChild {
constructor( constructor(
private readonly _permissionsService: IqserPermissionsService, private readonly _permissionsService: IqserPermissionsService,
private readonly _tenantContextHolder: TenantContextHolder, private readonly _tenantsService: TenantsService,
private readonly _rolesService: IqserRolesService, private readonly _rolesService: IqserRolesService,
private readonly _router: Router, private readonly _router: Router,
) {} ) {}
@ -85,10 +85,10 @@ export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivate
} }
if (Array.isArray(_redirectTo)) { if (Array.isArray(_redirectTo)) {
return this._router.navigate([this._tenantContextHolder.currentTenant, ..._redirectTo]); return this._router.navigate([this._tenantsService.currentTenant, ..._redirectTo]);
} }
return this._router.navigate([`${this._tenantContextHolder.currentTenant}${_redirectTo}`]); return this._router.navigate([`${this._tenantsService.currentTenant}${_redirectTo}`]);
} }
#checkRedirect(permissions: IqserPermissionsData, route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) { #checkRedirect(permissions: IqserPermissionsData, route: ActivatedRouteSnapshot | Route, state?: RouterStateSnapshot) {

View File

@ -1,4 +1,3 @@
export * from './tenant-context-holder';
export * from './tenants.service'; export * from './tenants.service';
export * from './tenant-id-interceptor'; export * from './tenant-id-interceptor';
export * from './tenant-id-response-interceptor'; export * from './tenant-id-response-interceptor';

View File

@ -1,7 +1,7 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
import { getConfig } from '../../services'; import { getConfig } from '../../services';
import { TenantContextHolder } from '../index'; import { TenantsService } from '../index';
import { BASE_HREF } from '../../utils'; import { BASE_HREF } from '../../utils';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
@ -9,7 +9,7 @@ import { NGXLogger } from 'ngx-logger';
export class KeycloakStatusService { export class KeycloakStatusService {
readonly #keycloakService = inject(KeycloakService); readonly #keycloakService = inject(KeycloakService);
readonly #config = getConfig(); readonly #config = getConfig();
readonly #tenantContextHolder = inject(TenantContextHolder); readonly #tenantsService = inject(TenantsService);
readonly #baseHref = inject(BASE_HREF); readonly #baseHref = inject(BASE_HREF);
readonly #logger = inject(NGXLogger); readonly #logger = inject(NGXLogger);
@ -30,7 +30,7 @@ export class KeycloakStatusService {
} }
createLoginUrl() { createLoginUrl() {
const currentTenant = this.#tenantContextHolder.currentTenant; const currentTenant = this.#tenantsService.currentTenant;
if (currentTenant && window.location.href.indexOf('/' + currentTenant) > 0) { if (currentTenant && window.location.href.indexOf('/' + currentTenant) > 0) {
return window.location.href; return window.location.href;
} }

View File

@ -1,11 +0,0 @@
import { inject, Injectable } from '@angular/core';
import { TenantsService } from './tenants.service';
@Injectable({ providedIn: 'root' })
export class TenantContextHolder {
readonly #tenantsService = inject(TenantsService);
get currentTenant() {
return this.#tenantsService.currentTenant;
}
}

View File

@ -1,16 +1,16 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { TenantContextHolder } from './tenant-context-holder'; import { TenantsService } from './tenants.service';
@Injectable() @Injectable()
export class TenantIdInterceptor implements HttpInterceptor { export class TenantIdInterceptor implements HttpInterceptor {
protected readonly _tenantContext = inject(TenantContextHolder); protected readonly _tenantsService = inject(TenantsService);
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
if (this._tenantContext.currentTenant) { if (this._tenantsService.currentTenant) {
const updatedRequest = req.clone({ const updatedRequest = req.clone({
setHeaders: { 'X-TENANT-ID': this._tenantContext.currentTenant }, setHeaders: { 'X-TENANT-ID': this._tenantsService.currentTenant },
}); });
return next.handle(updatedRequest); return next.handle(updatedRequest);

View File

@ -1,6 +1,6 @@
import { inject, Injectable } from '@angular/core'; import { computed, inject, Injectable, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { BASE_HREF } from '../../utils'; import { BASE_HREF } from '../../utils';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@ -13,8 +13,11 @@ export interface IBaseTenant {
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TenantsService { export class TenantsService {
hasMultipleTenants = false; readonly tenants = signal<IBaseTenant[] | undefined>(undefined);
readonly tenantData$ = new BehaviorSubject<IBaseTenant[] | undefined>(undefined); readonly hasMultiple = computed(() => {
const tenants = this.tenants();
return tenants ? tenants.length > 1 : false;
});
readonly #http = inject(HttpClient); readonly #http = inject(HttpClient);
readonly #router = inject(Router); readonly #router = inject(Router);
readonly #logger = inject(NGXLogger); readonly #logger = inject(NGXLogger);
@ -27,17 +30,16 @@ export class TenantsService {
removeItem: localStorage.removeItem.bind(localStorage), removeItem: localStorage.removeItem.bind(localStorage),
key: localStorage.key.bind(localStorage), key: localStorage.key.bind(localStorage),
}; };
readonly #activeTenantId$ = new BehaviorSubject<string>(''); readonly #activeTenantId = signal<string>('');
get currentTenant() { get currentTenant() {
return this.#activeTenantId$.value; return this.#activeTenantId();
} }
async loadTenants() { async loadTenants() {
this.#logger.info('[TENANTS] Loading tenants...'); this.#logger.info('[TENANTS] Loading tenants...');
const tenants = await firstValueFrom(this.#http.get<IBaseTenant[]>('/tenants/simple')); const tenants = await firstValueFrom(this.#http.get<IBaseTenant[]>('/tenants/simple'));
this.hasMultipleTenants = tenants.length > 1; this.tenants.set(tenants);
this.tenantData$.next(tenants);
const tenant = this.getTenantFromRoute(); const tenant = this.getTenantFromRoute();
if (tenant) { if (tenant) {
@ -50,7 +52,7 @@ export class TenantsService {
this.#logger.info('[TENANTS] No tenant in route'); this.#logger.info('[TENANTS] No tenant in route');
if (!this.hasMultipleTenants) { if (!this.hasMultiple) {
this.#logger.info('[TENANTS] Only one tenant loaded, auto-select it and redirect to login page'); this.#logger.info('[TENANTS] Only one tenant loaded, auto-select it and redirect to login page');
const tenant = tenants[0].tenantId; const tenant = tenants[0].tenantId;
if (await this.selectTenant(tenant)) { if (await this.selectTenant(tenant)) {
@ -66,7 +68,7 @@ export class TenantsService {
} }
async selectTenant(tenantId: string) { async selectTenant(tenantId: string) {
const tenants = this.tenantData$.value; const tenants = this.tenants();
if (!tenants) { if (!tenants) {
throw new Error('Tenants not loaded!'); throw new Error('Tenants not loaded!');
} }
@ -80,13 +82,13 @@ export class TenantsService {
} }
this.#mutateStorage(tenantId); this.#mutateStorage(tenantId);
this.setCurrentTenantId(tenantId); this.#setCurrentTenantId(tenantId);
return true; return true;
} }
setCurrentTenantId(tenantId: string) { #setCurrentTenantId(tenantId: string) {
this.#logger.info('[TENANTS] Set current tenant id: ', tenantId); this.#logger.info('[TENANTS] Set current tenant id: ', tenantId);
this.#activeTenantId$.next(tenantId); this.#activeTenantId.set(tenantId);
} }
#mutateStorage(tenant: string) { #mutateStorage(tenant: string) {

View File

@ -1,5 +1,5 @@
import { inject, Pipe, PipeTransform } from '@angular/core'; import { inject, Pipe, PipeTransform } from '@angular/core';
import { TenantContextHolder } from './services'; import { TenantsService } from './services';
@Pipe({ @Pipe({
name: 'tenant', name: 'tenant',
@ -7,13 +7,13 @@ import { TenantContextHolder } from './services';
standalone: true, standalone: true,
}) })
export class TenantPipe implements PipeTransform { export class TenantPipe implements PipeTransform {
readonly #tenant = inject(TenantContextHolder); readonly #tenantsService = inject(TenantsService);
transform(value: string | string[]): string | undefined { transform(value: string | string[]): string | undefined {
if (!value) { if (!value) {
return undefined; return undefined;
} }
const _value = Array.isArray(value) ? value.join('/') : value; const _value = Array.isArray(value) ? value.join('/') : value;
return '/' + this.#tenant.currentTenant + _value; return '/' + this.#tenantsService.currentTenant + _value;
} }
} }

View File

@ -1,18 +1,20 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular'; import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
import { IqserConfigService } from '../../services'; import { getConfig } from '../../services';
import { IqserUserService } from '../services/iqser-user.service'; import { IqserUserService } from '../services/iqser-user.service';
import { TenantContextHolder } from '../../tenants'; import { TenantsService } from '../../tenants';
import { KeycloakLoginOptions } from 'keycloak-js';
@Injectable() @Injectable()
export class IqserAuthGuard extends KeycloakAuthGuard { export class IqserAuthGuard extends KeycloakAuthGuard {
readonly #config = getConfig();
constructor( constructor(
protected readonly _router: Router, protected readonly _router: Router,
protected readonly _keycloak: KeycloakService, protected readonly _keycloak: KeycloakService,
private readonly _configService: IqserConfigService,
private readonly _userService: IqserUserService, private readonly _userService: IqserUserService,
private readonly _tenantContextHolder: TenantContextHolder, private readonly _tenantsService: TenantsService,
) { ) {
super(_router, _keycloak); super(_router, _keycloak);
} }
@ -20,11 +22,12 @@ export class IqserAuthGuard extends KeycloakAuthGuard {
async isAccessAllowed(route: ActivatedRouteSnapshot): Promise<boolean> { async isAccessAllowed(route: ActivatedRouteSnapshot): Promise<boolean> {
if (!this.authenticated) { if (!this.authenticated) {
const kcIdpHint = route.queryParamMap.get('kc_idp_hint'); const kcIdpHint = route.queryParamMap.get('kc_idp_hint');
const options: any = { const options: KeycloakLoginOptions = {
redirectUri: window.location.href, redirectUri: window.location.href,
}; };
if (kcIdpHint ?? this._configService.values.OAUTH_IDP_HINT) {
options.idpHint = kcIdpHint ?? this._configService.values.OAUTH_IDP_HINT; if (kcIdpHint ?? this.#config.OAUTH_IDP_HINT) {
options.idpHint = kcIdpHint ?? this.#config.OAUTH_IDP_HINT;
} }
await this._keycloak.login(options); await this._keycloak.login(options);
return false; return false;
@ -32,7 +35,7 @@ export class IqserAuthGuard extends KeycloakAuthGuard {
const user = await this._userService.loadCurrentUser(); const user = await this._userService.loadCurrentUser();
if (user?.hasAnyRole && route.routeConfig?.path === 'auth-error') { if (user?.hasAnyRole && route.routeConfig?.path === 'auth-error') {
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`]); await this._router.navigate([`/${this._tenantsService.currentTenant}/main`]);
return false; return false;
} }

View File

@ -2,19 +2,19 @@ import { inject, Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { LoadingService } from '../../loading'; import { LoadingService } from '../../loading';
import { IqserUserService } from '../services/iqser-user.service'; import { IqserUserService } from '../services/iqser-user.service';
import { TenantContextHolder } from '../../tenants'; import { TenantsService } from '../../tenants';
@Injectable() @Injectable()
export class IqserRoleGuard implements CanActivate { export class IqserRoleGuard implements CanActivate {
protected readonly _router = inject(Router); protected readonly _router = inject(Router);
protected readonly _tenantContextHolder = inject(TenantContextHolder); protected readonly _tenantsService = inject(TenantsService);
protected readonly _loadingService = inject(LoadingService); protected readonly _loadingService = inject(LoadingService);
protected readonly _userService = inject(IqserUserService); protected readonly _userService = inject(IqserUserService);
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const currentUser = this._userService.currentUser; const currentUser = this._userService.currentUser;
if (!currentUser || !currentUser.hasAnyRole) { if (!currentUser || !currentUser.hasAnyRole) {
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/auth-error`]); await this._router.navigate([`/${this._tenantsService.currentTenant}/auth-error`]);
this._loadingService.stop(); this._loadingService.stop();
return false; return false;
} }