RED-6713 update tenants handling
This commit is contained in:
parent
cc8d040654
commit
8ac4bbfd9f
@ -3,11 +3,8 @@ export * from './lib/buttons';
|
||||
export * from './lib/dialog';
|
||||
export * from './lib/form';
|
||||
export * from './lib/listing';
|
||||
export * from './lib/filtering';
|
||||
export * from './lib/help-mode';
|
||||
export * from './lib/inputs';
|
||||
export * from './lib/utils';
|
||||
export * from './lib/sorting';
|
||||
export * from './lib/services';
|
||||
export * from './lib/shared';
|
||||
export * from './lib/loading';
|
||||
@ -16,7 +13,6 @@ export * from './lib/search';
|
||||
export * from './lib/upload-file';
|
||||
export * from './lib/empty-state';
|
||||
export * from './lib/caching';
|
||||
export * from './lib/users';
|
||||
export * from './lib/translations';
|
||||
export * from './lib/pipes';
|
||||
export * from './lib/permissions';
|
||||
|
||||
@ -18,6 +18,7 @@ import { IqserRolesService } from './roles.service';
|
||||
import { isArray, isFunction, isRedirectWithParameters, isString, transformPermission } from '../utils';
|
||||
import { List } from '../../utils';
|
||||
import { TenantsService } from '../../tenants';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
export interface IqserPermissionsData {
|
||||
readonly allow: string | List;
|
||||
@ -33,6 +34,7 @@ export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivate
|
||||
private readonly _tenantsService: TenantsService,
|
||||
private readonly _rolesService: IqserRolesService,
|
||||
private readonly _router: Router,
|
||||
private readonly _logger: NGXLogger,
|
||||
) {}
|
||||
|
||||
canActivate(route: IqserActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
@ -81,13 +83,39 @@ export class IqserPermissionsGuard implements CanActivate, CanMatch, CanActivate
|
||||
: navigationCommands;
|
||||
let navigationExtras = _redirectTo.navigationExtras ?? {};
|
||||
navigationExtras = isFunction<NavigationExtrasFn>(navigationExtras) ? navigationExtras(route, state) : navigationExtras;
|
||||
return this._router.navigate(navigationCommands, navigationExtras);
|
||||
this._logger.warn(
|
||||
'Could not activate route ',
|
||||
route,
|
||||
' because of missing permission ',
|
||||
failedPermissionName,
|
||||
'. Redirecting to ',
|
||||
navigationCommands,
|
||||
' with extras ',
|
||||
navigationExtras,
|
||||
);
|
||||
return this._router.navigate([this._tenantsService.activeTenantId, ...navigationCommands], navigationExtras);
|
||||
}
|
||||
|
||||
if (Array.isArray(_redirectTo)) {
|
||||
this._logger.warn(
|
||||
'Could not activate route ',
|
||||
route,
|
||||
' because of missing permission ',
|
||||
failedPermissionName,
|
||||
'. Redirecting to ',
|
||||
_redirectTo,
|
||||
);
|
||||
return this._router.navigate([this._tenantsService.activeTenantId, ..._redirectTo]);
|
||||
}
|
||||
|
||||
this._logger.warn(
|
||||
'Could not activate route ',
|
||||
route,
|
||||
' because of missing permission ',
|
||||
failedPermissionName,
|
||||
'. Redirecting to ',
|
||||
_redirectTo,
|
||||
);
|
||||
return this._router.navigate([`${this._tenantsService.activeTenantId}${_redirectTo}`]);
|
||||
}
|
||||
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { KeycloakStatusService } from '../services/keycloak-status.service';
|
||||
import { keycloakInitializer } from '../keycloak-initializer';
|
||||
import { TenantsService } from '../services';
|
||||
|
||||
export const ifLoggedIn: CanActivateFn = async (route: ActivatedRouteSnapshot) => {
|
||||
const logger = inject(NGXLogger);
|
||||
logger.info('[ROUTES] Check if can activate main');
|
||||
|
||||
const tenantsService = inject(TenantsService);
|
||||
const keycloakService = inject(KeycloakService);
|
||||
const keycloakStatusService = inject(KeycloakStatusService);
|
||||
|
||||
const keycloakInstance = keycloakService.getKeycloakInstance();
|
||||
const tenant = route.paramMap.get('tenant');
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
const username = queryParams.get('username');
|
||||
|
||||
if (!keycloakInstance) {
|
||||
if (!tenant) {
|
||||
logger.error('[ROUTES] No tenant found, something is wrong...');
|
||||
return inject(Router).navigate(['/']);
|
||||
}
|
||||
|
||||
logger.info('[KEYCLOAK] Keycloak init...');
|
||||
await keycloakInitializer(tenant);
|
||||
logger.info('[KEYCLOAK] Keycloak init done!');
|
||||
await tenantsService.selectTenant(tenant);
|
||||
}
|
||||
|
||||
const isLoggedIn = await keycloakService.isLoggedIn();
|
||||
|
||||
if (isLoggedIn) {
|
||||
logger.info('[ROUTES] Is logged in, continuing');
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.warn('[ROUTES] Redirect to login');
|
||||
keycloakStatusService.createLoginUrlAndExecute(username);
|
||||
return false;
|
||||
};
|
||||
@ -1,5 +1,4 @@
|
||||
export * from './keycloak-initializer';
|
||||
export * from './guards/if-logged-in.guard';
|
||||
export * from './guards/if-not-logged-in.guard';
|
||||
export * from './services';
|
||||
export * from './tenant.pipe';
|
||||
|
||||
@ -6,7 +6,7 @@ import { getConfig } from '../services';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
function getKeycloakOptions(baseUrl: string, config: IqserAppConfig, tenant: string): KeycloakOptions {
|
||||
export function getKeycloakOptions(baseUrl: string, config: IqserAppConfig, tenant: string): KeycloakOptions {
|
||||
return {
|
||||
config: {
|
||||
url: config.OAUTH_URL,
|
||||
@ -21,6 +21,7 @@ function getKeycloakOptions(baseUrl: string, config: IqserAppConfig, tenant: str
|
||||
enableLogging: true,
|
||||
},
|
||||
enableBearerInterceptor: true,
|
||||
loadUserProfileAtStartUp: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ export class KeycloakStatusService {
|
||||
const keycloakInstance = this.#keycloakService?.getKeycloakInstance();
|
||||
if (keycloakInstance) {
|
||||
const url = keycloakInstance.createLoginUrl({
|
||||
redirectUri: this.createLoginUrl(),
|
||||
redirectUri: this.createLoginUrl(this.#tenantsService.activeTenantId),
|
||||
idpHint: this.#config.OAUTH_IDP_HINT,
|
||||
loginHint: username ?? undefined,
|
||||
});
|
||||
@ -30,15 +30,14 @@ export class KeycloakStatusService {
|
||||
}
|
||||
}
|
||||
|
||||
createLoginUrl() {
|
||||
const currentTenant = this.#tenantsService.activeTenantId;
|
||||
if (currentTenant && window.location.href.indexOf('/' + currentTenant) > 0) {
|
||||
createLoginUrl(tenant?: string) {
|
||||
if (tenant && window.location.href.indexOf('/' + tenant + '/') > 0) {
|
||||
return window.location.href;
|
||||
}
|
||||
|
||||
const url = window.location.origin + this.#baseHref;
|
||||
if (currentTenant) {
|
||||
return url + '/' + currentTenant;
|
||||
if (tenant) {
|
||||
return url + '/' + tenant;
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
@ -1,17 +1,8 @@
|
||||
import { computed, inject, Injectable, signal } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { inject, Injectable, signal } from '@angular/core';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { BASE_HREF, List } from '../../utils';
|
||||
import { Router } from '@angular/router';
|
||||
import { List } from '../../utils';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export interface IBaseTenant {
|
||||
readonly tenantId: string;
|
||||
readonly displayName: string;
|
||||
readonly guid: string;
|
||||
}
|
||||
|
||||
export interface IStoredTenantId {
|
||||
readonly tenantId: string;
|
||||
readonly email: string;
|
||||
@ -20,28 +11,11 @@ export interface IStoredTenantId {
|
||||
|
||||
export type StoredTenantIds = List<IStoredTenantId>;
|
||||
|
||||
export interface IStoredTenant {
|
||||
readonly tenant: IBaseTenant;
|
||||
readonly email: string;
|
||||
readonly created: string;
|
||||
}
|
||||
|
||||
export type StoredTenants = IStoredTenant[];
|
||||
|
||||
const STORED_TENANTS_KEY = 'red-stored-tenants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TenantsService {
|
||||
protected readonly _serviceName: string = 'tenant-user-management';
|
||||
readonly tenants = signal<IBaseTenant[] | undefined>(undefined);
|
||||
readonly hasMultiple = computed(() => {
|
||||
const tenants = this.tenants();
|
||||
return tenants ? tenants.length > 1 : false;
|
||||
});
|
||||
readonly #http = inject(HttpClient);
|
||||
readonly #router = inject(Router);
|
||||
readonly #logger = inject(NGXLogger);
|
||||
readonly #baseHref = inject(BASE_HREF);
|
||||
readonly #storageReference: Storage = {
|
||||
length: localStorage.length,
|
||||
clear: localStorage.clear.bind(localStorage),
|
||||
@ -51,59 +25,14 @@ export class TenantsService {
|
||||
key: localStorage.key.bind(localStorage),
|
||||
};
|
||||
readonly #activeTenantId = signal<string>('');
|
||||
readonly activeTenant = computed(() => {
|
||||
return this.tenants()?.find(t => t.tenantId === this.#activeTenantId());
|
||||
});
|
||||
protected readonly _serviceName: string = 'tenant-user-management';
|
||||
readonly hasMultiple = this.getStoredTenants().length > 1;
|
||||
|
||||
get activeTenantId() {
|
||||
return this.#activeTenantId();
|
||||
}
|
||||
|
||||
async loadTenants() {
|
||||
this.#logger.info('[TENANTS] Loading tenants...');
|
||||
const tenants = await firstValueFrom(this.#http.get<IBaseTenant[]>(`/${this._serviceName}/tenants/simple`));
|
||||
this.tenants.set(tenants);
|
||||
|
||||
const tenant = this.getTenantFromRoute();
|
||||
if (tenant) {
|
||||
this.#logger.info('[TENANTS] Tenant from route: ', tenant);
|
||||
if (await this.selectTenant(tenant)) {
|
||||
await this.#executeMainResolverAndRedirect(tenant);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.#logger.info('[TENANTS] No tenant in route');
|
||||
|
||||
if (!this.hasMultiple) {
|
||||
this.#logger.info('[TENANTS] Only one tenant loaded, auto-select it and redirect to login page');
|
||||
const tenant = tenants[0].tenantId;
|
||||
if (await this.selectTenant(tenant)) {
|
||||
await this.#executeMainResolverAndRedirect(tenant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTenantFromRoute() {
|
||||
const path = window.location.pathname;
|
||||
const nextSlash = path.indexOf('/', this.#baseHref.length + 1);
|
||||
return path.substring(this.#baseHref.length + 1, nextSlash >= 0 ? nextSlash : path.length);
|
||||
}
|
||||
|
||||
async selectTenant(tenantId: string) {
|
||||
const tenants = this.tenants();
|
||||
if (!tenants) {
|
||||
throw new Error('Tenants not loaded!');
|
||||
}
|
||||
|
||||
const unknownTenant = !tenants.map(t => t.tenantId).includes(tenantId);
|
||||
|
||||
if (unknownTenant) {
|
||||
this.#logger.info('[TENANTS] Unknown tenant, redirecting to select tenant page');
|
||||
await this.#router.navigate(['/']);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.#mutateStorage(tenantId);
|
||||
this.#setActiveTenantId(tenantId);
|
||||
return true;
|
||||
@ -116,58 +45,43 @@ export class TenantsService {
|
||||
}
|
||||
|
||||
const storedTenants = this.getStoredTenants();
|
||||
const activeTenant = this.activeTenant();
|
||||
const existing = storedTenants.find(s => s.email === emailOrUsername && s.tenant.tenantId === activeTenant?.tenantId);
|
||||
const activeTenantId = this.#activeTenantId();
|
||||
const existing = storedTenants.find(s => s.email === emailOrUsername && s.tenantId === activeTenantId);
|
||||
if (existing) {
|
||||
this.#logger.info('[TENANTS] Stored tenant exists: ', storedTenants);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activeTenant) {
|
||||
if (!activeTenantId) {
|
||||
this.#logger.error('[TENANTS] Active tenant is null when storing tenants');
|
||||
return;
|
||||
}
|
||||
|
||||
storedTenants.push({ tenant: activeTenant, email: emailOrUsername, created: new Date().toISOString() });
|
||||
const serializableTenants = storedTenants.map(s => ({
|
||||
tenantId: s.tenant.tenantId,
|
||||
email: s.email,
|
||||
}));
|
||||
this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(serializableTenants));
|
||||
storedTenants.push({ tenantId: activeTenantId, email: emailOrUsername, created: new Date().toISOString() });
|
||||
this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(storedTenants));
|
||||
this.#logger.info('[TENANTS] Stored tenants: ', storedTenants);
|
||||
}
|
||||
|
||||
getStoredTenants(): StoredTenants {
|
||||
getStoredTenants() {
|
||||
const rawStoredTenants = this.#storageReference.getItem(STORED_TENANTS_KEY);
|
||||
const stored = JSON.parse(rawStoredTenants ?? '[]') as StoredTenantIds;
|
||||
const tenants = this.tenants();
|
||||
if (!tenants) {
|
||||
this.#logger.error('[TENANTS] Tenants data is null when retrieving stored tenants');
|
||||
return [];
|
||||
}
|
||||
|
||||
const trueStored: StoredTenants = [];
|
||||
const validStoredTenants: IStoredTenantId[] = [];
|
||||
for (const s of stored) {
|
||||
const tenant = tenants.find(t => t.tenantId === s.tenantId);
|
||||
if (!tenant) {
|
||||
this.#logger.warn(`[TENANTS] Stored tenant with id ${tenant} not found, skipping`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const date1 = dayjs(s.created);
|
||||
const date2 = dayjs();
|
||||
const diff = date2.diff(date1, 'days');
|
||||
const is90DaysOld = diff >= 90;
|
||||
if (is90DaysOld) {
|
||||
this.#logger.warn(`[TENANTS] Saved tenant ${tenant} - ${s.email} is 90 days old, delete it`);
|
||||
this.#logger.warn(`[TENANTS] Saved tenant ${s.tenantId} - ${s.email} is 90 days old, delete it`);
|
||||
this.removeStored(s.email);
|
||||
continue;
|
||||
}
|
||||
|
||||
trueStored.push({ tenant, email: s.email, created: s.created });
|
||||
validStoredTenants.push(s);
|
||||
}
|
||||
|
||||
return trueStored;
|
||||
return validStoredTenants;
|
||||
}
|
||||
|
||||
removeStored(email: string) {
|
||||
@ -177,27 +91,18 @@ export class TenantsService {
|
||||
}
|
||||
|
||||
const storedTenants = this.getStoredTenants();
|
||||
const activeTenant = this.activeTenant();
|
||||
const existing = storedTenants.find(s => s.email === email && s.tenant.tenantId === activeTenant?.tenantId);
|
||||
const activeTenantId = this.#activeTenantId();
|
||||
const existing = storedTenants.find(s => s.email === email && s.tenantId === activeTenantId);
|
||||
if (!existing) {
|
||||
this.#logger.info('[TENANTS] No stored tenant for ', email);
|
||||
return;
|
||||
}
|
||||
|
||||
const serializableTenants = storedTenants
|
||||
.filter(s => s !== existing)
|
||||
.map(s => ({
|
||||
tenantId: s.tenant.tenantId,
|
||||
email: s.email,
|
||||
}));
|
||||
const serializableTenants = storedTenants.filter(s => s !== existing);
|
||||
this.#storageReference.setItem(STORED_TENANTS_KEY, JSON.stringify(serializableTenants));
|
||||
this.#logger.info('[TENANTS] Stored tenants at logout: ', storedTenants);
|
||||
}
|
||||
|
||||
has(tenantId: string) {
|
||||
return !!this.tenants()?.some(t => t.tenantId === tenantId);
|
||||
}
|
||||
|
||||
#setActiveTenantId(tenantId: string) {
|
||||
this.#logger.info('[TENANTS] Set current tenant id: ', tenantId);
|
||||
this.#activeTenantId.set(tenantId);
|
||||
@ -214,11 +119,4 @@ export class TenantsService {
|
||||
this.#storageReference.removeItem(tenant + ':' + key);
|
||||
};
|
||||
}
|
||||
|
||||
async #executeMainResolverAndRedirect(tenant: string) {
|
||||
const intendedPath = window.location.pathname.replace(this.#baseHref, '');
|
||||
// TODO: Load roles in child routes canLoad functions so we can redirect straight to wanted URL
|
||||
await this.#router.navigate([tenant], { skipLocationChange: true });
|
||||
await this.#router.navigate([intendedPath]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,15 +17,14 @@
|
||||
|
||||
<div *ngIf="storedTenants.length">
|
||||
<div
|
||||
(click)="select(stored.tenantId, stored.email)"
|
||||
*ngFor="let stored of storedTenants"
|
||||
[queryParams]="{ username: stored.email }"
|
||||
[routerLink]="[stored.tenant.tenantId]"
|
||||
class="d-flex pointer mat-elevation-z2 card stored-tenant-card mt-10"
|
||||
>
|
||||
<iqser-logo class="card-icon" icon="red:logo" mat-card-image></iqser-logo>
|
||||
|
||||
<div class="card-content flex-column">
|
||||
<span class="heading">{{ stored.tenant.displayName }}</span>
|
||||
<span class="heading">{{ stored.tenantId }}</span>
|
||||
<span>{{ stored.email }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { TenantsService } from '../services';
|
||||
import { LoadingService } from '../../loading';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { Toaster } from '../../services';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { getConfig } from '../../services';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { BASE_HREF } from '../../utils';
|
||||
import { getKeycloakOptions } from '../keycloak-initializer';
|
||||
import { KeycloakStatusService } from '../services/keycloak-status.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './tenant-select.component.html',
|
||||
@ -13,20 +15,20 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TenantSelectComponent {
|
||||
@Input() isLoggedOut = false;
|
||||
|
||||
readonly loadingService = inject(LoadingService);
|
||||
readonly form = inject(FormBuilder).group({
|
||||
protected readonly baseHref = inject(BASE_HREF);
|
||||
protected readonly storedTenants = inject(TenantsService)
|
||||
.getStoredTenants()
|
||||
.sort((a, b) => a.tenantId.localeCompare(b.tenantId));
|
||||
protected readonly titleService = inject(Title);
|
||||
protected readonly config = getConfig();
|
||||
protected readonly loadingService = inject(LoadingService);
|
||||
protected readonly keycloakService = inject(KeycloakService);
|
||||
protected readonly keycloakStatusService = inject(KeycloakStatusService);
|
||||
protected readonly form = inject(FormBuilder).group({
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
tenantId: ['', Validators.required],
|
||||
});
|
||||
protected readonly tenantsService = inject(TenantsService);
|
||||
protected readonly storedTenants = this.tenantsService
|
||||
.getStoredTenants()
|
||||
.sort((a, b) => a.tenant.displayName.localeCompare(b.tenant.displayName));
|
||||
protected readonly titleService = inject(Title);
|
||||
protected readonly toaster = inject(Toaster);
|
||||
readonly #router = inject(Router);
|
||||
@Input() isLoggedOut = false;
|
||||
|
||||
updateTenantSelection() {
|
||||
const tenantId = this.form.controls.tenantId.value;
|
||||
@ -34,23 +36,28 @@ export class TenantSelectComponent {
|
||||
throw new Error('No tenant selected!');
|
||||
}
|
||||
|
||||
if (!this.tenantsService.has(tenantId)) {
|
||||
this.toaster.error(_('tenant-resolve.tenant-does-not-exist'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingService.start();
|
||||
|
||||
const queryParams = this.#getQueryParams(tenantId);
|
||||
return this.#router.navigate([tenantId], { queryParams });
|
||||
const email = this.#getEmail(tenantId);
|
||||
return this.select(tenantId, email);
|
||||
}
|
||||
|
||||
#getQueryParams(tenantId: string) {
|
||||
const existingStored = this.storedTenants.filter(s => s.tenant.tenantId === tenantId);
|
||||
if (existingStored.length === 1) {
|
||||
return { username: existingStored[0].email };
|
||||
async select(tenantId: string, email?: string) {
|
||||
try {
|
||||
await this.keycloakService.init(getKeycloakOptions(this.baseHref, this.config, tenantId));
|
||||
} catch (e) {
|
||||
return this.keycloakService.logout();
|
||||
}
|
||||
const url = this.keycloakService.getKeycloakInstance().createLoginUrl({
|
||||
redirectUri: this.keycloakStatusService.createLoginUrl(tenantId),
|
||||
idpHint: this.config.OAUTH_IDP_HINT,
|
||||
loginHint: email ?? undefined,
|
||||
});
|
||||
return this.keycloakService.logout(url);
|
||||
}
|
||||
|
||||
return {};
|
||||
#getEmail(tenantId: string) {
|
||||
const existingStored = this.storedTenants.filter(s => s.tenantId === tenantId);
|
||||
return existingStored.length === 1 ? existingStored[0].email : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { APP_INITIALIZER, inject, ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
@ -7,19 +7,17 @@ import { TenantSelectComponent } from './tenant-select/tenant-select.component';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { TenantIdInterceptor, TenantIdResponseInterceptor, TenantsService } from './services';
|
||||
import { TenantIdInterceptor, TenantIdResponseInterceptor } from './services';
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { LogoComponent } from '../shared/logo/logo.component';
|
||||
import { LogoComponent } from '../shared';
|
||||
import { SpacerComponent } from '../shared/spacer/spacer.component';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
const components = [TenantSelectComponent];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components],
|
||||
declarations: [TenantSelectComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatDialogModule,
|
||||
@ -36,7 +34,7 @@ const components = [TenantSelectComponent];
|
||||
MatButtonModule,
|
||||
RouterLink,
|
||||
],
|
||||
exports: [...components],
|
||||
exports: [TenantSelectComponent],
|
||||
})
|
||||
export class TenantsModule {
|
||||
static forRoot(): ModuleWithProviders<TenantsModule> {
|
||||
@ -53,14 +51,6 @@ export class TenantsModule {
|
||||
multi: true,
|
||||
useClass: TenantIdResponseInterceptor,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
useFactory() {
|
||||
const tenantsService = inject(TenantsService);
|
||||
return () => tenantsService.loadTenants();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
17
src/lib/users/guards/has-roles.guard.ts
Normal file
17
src/lib/users/guards/has-roles.guard.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { CanActivateFn, Router } from '@angular/router';
|
||||
import { inject } from '@angular/core';
|
||||
import { IqserUserService } from '../services/iqser-user.service';
|
||||
import { TenantsService } from '../../tenants';
|
||||
|
||||
export function hasAnyRoleGuard(): CanActivateFn {
|
||||
return async () => {
|
||||
const router = inject(Router);
|
||||
const activeTenantId = inject(TenantsService).activeTenantId;
|
||||
const user = await inject(IqserUserService).loadCurrentUser();
|
||||
if (user?.hasAnyRole) {
|
||||
await router.navigate([`/${activeTenantId}/main`]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
@ -2,43 +2,30 @@ import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Router } from '@angular/router';
|
||||
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
|
||||
import { getConfig } from '../../services';
|
||||
import { IqserUserService } from '../services/iqser-user.service';
|
||||
import { TenantsService } from '../../tenants';
|
||||
import { KeycloakLoginOptions } from 'keycloak-js';
|
||||
|
||||
@Injectable()
|
||||
export class IqserAuthGuard extends KeycloakAuthGuard {
|
||||
readonly #config = getConfig();
|
||||
|
||||
constructor(
|
||||
protected readonly _router: Router,
|
||||
protected readonly _keycloak: KeycloakService,
|
||||
private readonly _userService: IqserUserService,
|
||||
private readonly _tenantsService: TenantsService,
|
||||
) {
|
||||
constructor(protected readonly _router: Router, protected readonly _keycloak: KeycloakService) {
|
||||
super(_router, _keycloak);
|
||||
}
|
||||
|
||||
async isAccessAllowed(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
if (!this.authenticated) {
|
||||
const kcIdpHint = route.queryParamMap.get('kc_idp_hint');
|
||||
const options: KeycloakLoginOptions = {
|
||||
redirectUri: window.location.href,
|
||||
};
|
||||
|
||||
if (kcIdpHint ?? this.#config.OAUTH_IDP_HINT) {
|
||||
options.idpHint = kcIdpHint ?? this.#config.OAUTH_IDP_HINT;
|
||||
}
|
||||
await this._keycloak.login(options);
|
||||
return false;
|
||||
if (this.authenticated) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const user = await this._userService.loadCurrentUser();
|
||||
if (user?.hasAnyRole && route.routeConfig?.path === 'auth-error') {
|
||||
await this._router.navigate([`/${this._tenantsService.activeTenantId}/main`]);
|
||||
return false;
|
||||
}
|
||||
const kcIdpHint = route.queryParamMap.get('kc_idp_hint');
|
||||
const options: KeycloakLoginOptions = {
|
||||
redirectUri: window.location.href,
|
||||
};
|
||||
|
||||
return true;
|
||||
if (kcIdpHint ?? this.#config.OAUTH_IDP_HINT) {
|
||||
options.idpHint = kcIdpHint ?? this.#config.OAUTH_IDP_HINT;
|
||||
}
|
||||
await this._keycloak.login(options);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,5 +11,6 @@ export * from './services/default-user.service';
|
||||
export * from './iqser-users.module';
|
||||
export * from './guards/iqser-auth-guard.service';
|
||||
export * from './guards/iqser-role-guard.service';
|
||||
export * from './guards/has-roles.guard';
|
||||
export * from './components/user-button/user-button.component';
|
||||
export * from './components/initials-avatar/initials-avatar.component';
|
||||
|
||||
@ -12,11 +12,12 @@ export class IqserUser implements IIqserUser, IListable {
|
||||
readonly searchKey: string;
|
||||
readonly active?: boolean;
|
||||
|
||||
readonly hasAnyRole = this.roles.length > 0;
|
||||
readonly hasAnyRole: boolean;
|
||||
|
||||
constructor(user: KeycloakProfile | IIqserUser, ...args: unknown[]);
|
||||
|
||||
constructor(user: KeycloakProfile | IIqserUser, readonly roles: List, readonly userId: string) {
|
||||
this.hasAnyRole = this.roles.length > 0;
|
||||
this.email = user.email;
|
||||
this.username = user.username ?? this.email ?? 'unknown user';
|
||||
this.firstName = user.firstName;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user