Merge branch 'RED-6523'

This commit is contained in:
Valentin Mihai 2023-03-31 15:52:05 +03:00
commit 9bb7619907
25 changed files with 441 additions and 35 deletions

View File

@ -21,3 +21,4 @@ export * from './lib/translations';
export * from './lib/pipes';
export * from './lib/permissions';
export * from './lib/directives';
export * from './lib/tenants';

View File

@ -6,7 +6,6 @@ export const APP_LEVEL_CACHE = 'app-level-cache';
export const DYNAMIC_CACHES = new InjectionToken<DynamicCaches>('dynamic-caches');
export async function wipeAllCaches() {
console.log('get caches keys');
const keys = (await caches?.keys()) ?? [];
for (const cache of keys) {
await wipeCache(cache);
@ -14,7 +13,6 @@ export async function wipeAllCaches() {
}
export function wipeCache(cacheName: string) {
console.log('delete cache: ', cacheName);
return caches?.delete(cacheName);
}
@ -23,7 +21,6 @@ export async function wipeCacheEntry(cacheName: string, entry: string) {
return;
}
console.log('open cache: ', cacheName);
const cache = await caches.open(cacheName);
return cache.delete(entry, { ignoreSearch: false });
}

View File

@ -6,6 +6,7 @@ import { MAX_RETRIES_ON_SERVER_ERROR, SERVER_ERROR_SKIP_PATHS } from './tokens';
import { ErrorService } from './error.service';
import { KeycloakService } from 'keycloak-angular';
import { IqserConfigService } from '../services';
import { KeycloakStatusService } from '../users';
function updateSeconds(seconds: number) {
if (seconds === 0 || seconds === 1) {
@ -49,6 +50,7 @@ export class ServerErrorInterceptor implements HttpInterceptor {
private readonly _errorService: ErrorService,
private readonly _keycloakService: KeycloakService,
private readonly _configService: IqserConfigService,
private readonly _keycloakStatusService: KeycloakStatusService,
@Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number,
@Optional() @Inject(SERVER_ERROR_SKIP_PATHS) private readonly _skippedPaths: string[],
) {}
@ -58,10 +60,7 @@ export class ServerErrorInterceptor implements HttpInterceptor {
catchError((error: HttpErrorResponse) => {
// token expired
if (error.status === HttpStatusCode.Unauthorized) {
window.location.href = this._keycloakService.getKeycloakInstance().createLoginUrl({
redirectUri: window.location.href,
idpHint: this._configService.values.OAUTH_IDP_HINT,
});
this._keycloakStatusService.createLoginUrlAndExecute();
}
// server error

View File

@ -21,6 +21,7 @@ import { CircleButtonComponent, IconButtonComponent } from '../buttons';
import { MatIconModule } from '@angular/material/icon';
import { EmptyStateComponent } from '../empty-state';
import { InputWithActionComponent, RoundCheckboxComponent } from '../inputs';
import { TenantPipe } from '../tenants/tenant.pipe';
const matModules = [MatTooltipModule, MatIconModule];
const components = [
@ -50,6 +51,7 @@ const modules = [DragDropModule, TranslateModule, IqserFiltersModule, ScrollingM
RoundCheckboxComponent,
InputWithActionComponent,
SyncWidthDirective,
TenantPipe,
],
})
export class IqserListingModule {}

View File

@ -17,7 +17,7 @@
[class.help-mode]="helpModeService?.isHelpModeActive$ | async"
[id]="'item-' + entity.id"
[ngClass]="getTableItemClasses(entity)"
[routerLink]="entity.routerLink"
[routerLink]="entity.routerLink | tenant"
>
<iqser-table-item
(click)="multiSelect(entity, $event)"
@ -31,7 +31,7 @@
[class.help-mode]="helpModeService?.isHelpModeActive$ | async"
[id]="'item-' + entity.id"
[ngClass]="getTableItemClasses(entity)"
[routerLink]="entity.routerLink"
[routerLink]="entity.routerLink | tenant"
>
<iqser-table-item
(click)="multiSelect(entity, $event)"

View File

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

View File

@ -1,3 +1,5 @@
import exp from 'constants';
export * from './toaster.service';
export * from './error-message.service';
export * from './generic.service';

4
src/lib/tenants/index.ts Normal file
View File

@ -0,0 +1,4 @@
export * from './services';
export * from './tenant.pipe';
export * from './tenants.module';
export * from './tenant-resolve/tenant-resolve.component';

View File

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

View File

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class TenantContextHolder {
readonly #activeTenantId$ = new BehaviorSubject<string>('');
setCurrentTenantId(tenantId: string) {
this.#activeTenantId$.next(tenantId);
}
get currentTenant() {
return this.#activeTenantId$.value;
}
}

View File

@ -0,0 +1,69 @@
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { NGXLogger } from 'ngx-logger';
import { tap } from 'rxjs/operators';
import { IqserConfigService } from '../../services/iqser-config.service';
import { TenantContextHolder } from './tenant-context-holder';
import { ActivatedRoute } from '@angular/router';
import { BASE_HREF } from '../../utils';
export interface IBaseTenant {
tenantId: string;
displayName: string;
guid: string;
}
@Injectable({ providedIn: 'root' })
export class TenantContext {
hasMultipleTenants = false;
readonly tenantData$ = new BehaviorSubject<IBaseTenant[] | undefined>(undefined);
readonly tenantsReady$ = new BehaviorSubject<boolean>(false);
protected readonly _http = inject(HttpClient);
protected readonly _configService = inject(IqserConfigService);
protected readonly _tenantContextHolder = inject(TenantContextHolder);
protected readonly _route = inject(ActivatedRoute);
protected _storageReference: any;
constructor(private readonly _logger: NGXLogger) {
this._storageReference = {
getItem: localStorage.getItem.bind(localStorage),
setItem: localStorage.setItem.bind(localStorage),
removeItem: localStorage.removeItem.bind(localStorage),
key: localStorage.key.bind(localStorage),
};
}
loadTenants() {
const base = inject(BASE_HREF);
const path = window.location.pathname;
const nextSlash = path.indexOf('/', base.length + 1);
const tenant = path.substring(base.length + 1, nextSlash >= 0 ? nextSlash : path.length);
return this._http.get<IBaseTenant[]>('/tenants/simple').pipe(
tap(tenants => {
this.hasMultipleTenants = tenants.length > 1;
this.tenantData$.next(tenants);
this.tenantSelected(tenant);
this.tenantsReady$.next(true);
}),
);
}
tenantSelected(tenantId: string) {
if (this.tenantData$.value?.map(t => t.tenantId).includes(tenantId)) {
this._mutateStorage(tenantId);
this._tenantContextHolder.setCurrentTenantId(tenantId);
}
}
private _mutateStorage(tenant: string) {
localStorage.getItem = (key: string) => {
return this._storageReference.getItem(tenant + ':' + key);
};
localStorage.setItem = (key: string, value: string) => {
this._storageReference.setItem(tenant + ':' + key, value);
};
localStorage.removeItem = (key: string) => {
this._storageReference.removeItem(tenant + ':' + key);
};
}
}

View File

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

View File

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
const VALID_TENANT_IDS = ['redaction'];
@Injectable()
export class TenantIdResponseInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
const xTenantId = event.headers.get('X-TENANT-ID');
if (VALID_TENANT_IDS.includes(xTenantId)) {
//TODO add logic to deny the response when backend will send the header
}
}
}),
);
}
}

View File

@ -0,0 +1,25 @@
<div class="tenant-section" *ngIf="!(loadingService.isLoading$ | async)">
<form (submit)="updateTenantSelection()" [formGroup]="form">
<div class="heading-l" translate="tenant-resolve.header"></div>
<div class="iqser-input-group required w-400">
<mat-form-field>
<mat-select
[placeholder]="'tenant-resolve.form.tenant-placeholder' | translate"
class="full-width"
formControlName="tenantId"
>
<mat-option *ngFor="let option of _tenantContext.tenantData$ | async" [value]="option.tenantId">
{{ option.displayName || option.tenantId }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<iqser-icon-button
[label]="'tenant-resolve.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
</form>
</div>

View File

@ -0,0 +1,13 @@
.tenant-section {
display: flex;
align-items: flex-start;
justify-content: center;
width: 100vw;
height: 100vh;
margin-top: 32px;
> form > * {
padding: 8px;
}
}

View File

@ -0,0 +1,54 @@
import { Component, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { TenantContext } from '../services/tenant-context';
import { IconButtonTypes } from '../../buttons/types/icon-button.type';
import { KeycloakService } from 'keycloak-angular';
import { BASE_HREF } from '../../utils';
import { LoadingService } from '../../loading';
@Component({
selector: 'iqser-tenant-resolve',
templateUrl: './tenant-resolve.component.html',
styleUrls: ['./tenant-resolve.component.scss'],
})
export class TenantResolveComponent {
private readonly _route = inject(ActivatedRoute);
readonly loadingService = inject(LoadingService);
private readonly _router = inject(Router);
private readonly _formBuilder = inject(FormBuilder);
private readonly _baseHref = inject(BASE_HREF);
private readonly _keycloakService = inject(KeycloakService);
protected _tenantContext = inject(TenantContext);
readonly iconButtonTypes = IconButtonTypes;
form?: UntypedFormGroup;
constructor() {
this.loadingService.start();
this.form = this._formBuilder.group({
tenantId: [undefined, Validators.required],
});
if (this._route.snapshot.paramMap.get('tenant')) {
this._keycloakService.isLoggedIn().then(isLoggedIn => {
if (isLoggedIn && this._route.snapshot.paramMap.get('tenant')) {
this.loadingService.stop();
this._router.navigate([this._route.snapshot.paramMap.get('tenant'), 'main']);
}
});
} else {
if (!this._tenantContext.hasMultipleTenants) {
const singleTenant = this._tenantContext.tenantData$.value[0];
window.location.href = window.location.origin + this._baseHref + '/' + singleTenant.tenantId;
}
}
this.loadingService.stop();
}
async updateTenantSelection() {
console.log('update selection');
const tenant = this.form.get('tenantId').value;
console.log('update to: ', tenant);
window.location.href = window.location.origin + this._baseHref + '/' + tenant;
}
}

View File

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

View File

@ -0,0 +1,61 @@
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { MatDialogModule } from '@angular/material/dialog';
import { CircleButtonComponent, IconButtonComponent } from '../buttons';
import { TenantResolveComponent } from './tenant-resolve/tenant-resolve.component';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { ReactiveFormsModule } from '@angular/forms';
import { IqserConfigService } from '../services/iqser-config.service';
import { TenantContext, TenantIdInterceptor, TenantIdResponseInterceptor } from './services';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { lastValueFrom } from 'rxjs';
import { IqserLoadingModule } from '../loading';
const components = [TenantResolveComponent];
@NgModule({
declarations: [...components],
imports: [
CommonModule,
MatDialogModule,
TranslateModule,
CircleButtonComponent,
MatInputModule,
MatSelectModule,
IconButtonComponent,
ReactiveFormsModule,
],
exports: [...components],
})
export class TenantsModule {
static forRoot(): ModuleWithProviders<TenantsModule> {
return {
ngModule: TenantsModule,
providers: [
{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: TenantIdInterceptor,
},
{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: TenantIdResponseInterceptor,
},
{
provide: APP_INITIALIZER,
multi: true,
useFactory: tenantInitializer,
deps: [IqserConfigService, TenantContext],
},
],
};
}
}
export function tenantInitializer(configService: IqserConfigService, tenantContext: TenantContext) {
const tenants = lastValueFrom(tenantContext.loadTenants());
return () => tenants;
}

View File

@ -3,6 +3,7 @@ import { ActivatedRouteSnapshot, Router } from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
import { IqserConfigService } from '../../services';
import { IqserUserService } from '../services/iqser-user.service';
import { TenantContextHolder } from '../../tenants';
@Injectable()
export class IqserAuthGuard extends KeycloakAuthGuard {
@ -11,6 +12,7 @@ export class IqserAuthGuard extends KeycloakAuthGuard {
protected readonly _keycloak: KeycloakService,
private readonly _configService: IqserConfigService,
private readonly _userService: IqserUserService,
private readonly _tenantContextHolder: TenantContextHolder,
) {
super(_router, _keycloak);
}
@ -30,7 +32,7 @@ export class IqserAuthGuard extends KeycloakAuthGuard {
const user = await this._userService.loadCurrentUser();
if (user?.hasAnyRole && route.routeConfig?.path === 'auth-error') {
await this._router.navigate(['/main']);
await this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main`]);
return false;
}

View File

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

View File

@ -8,6 +8,7 @@ export * from './types/name-pipe-options';
export * from './iqser-user.model';
export * from './services/iqser-user.service';
export * from './services/default-user.service';
export * from './services/keycloak-status.service';
export * from './iqser-users.module';
export * from './guards/iqser-auth-guard.service';
export * from './guards/iqser-role-guard.service';

View File

@ -1,6 +1,6 @@
import { APP_INITIALIZER, ModuleWithProviders, NgModule } from '@angular/core';
import { KeycloakAngularModule, KeycloakOptions, KeycloakService } from 'keycloak-angular';
import { KeycloakAngularModule, KeycloakEventType, KeycloakOptions, KeycloakService } from 'keycloak-angular';
import { DefaultUserService } from './services/default-user.service';
import { IIqserUser } from './types/user.response';
import { IqserUserService } from './services/iqser-user.service';
@ -18,16 +18,17 @@ import { UserButtonComponent } from './components/user-button/user-button.compon
import { MatIconModule } from '@angular/material/icon';
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
import { TranslateModule } from '@ngx-translate/core';
import { TenantContext, TenantContextHolder } from '../tenants';
import { filter, firstValueFrom, of, switchMap } from 'rxjs';
import { KeycloakStatus, KeycloakStatusService } from './services/keycloak-status.service';
import { map, tap } from 'rxjs/operators';
function getKeycloakOptions(baseUrl: string, configService: IqserConfigService): KeycloakOptions {
let url: string = configService.values.OAUTH_URL;
url = url.replace(/\/$/, ''); // remove trailing slash
const realm = url.substring(url.lastIndexOf('/') + 1, url.length);
url = url.substring(0, url.lastIndexOf('/realms'));
function getKeycloakOptions(baseUrl: string, tenantContextHolder: TenantContextHolder, configService: IqserConfigService): KeycloakOptions {
console.log('keycloak config for: ', tenantContextHolder.currentTenant);
return {
config: {
url: url,
realm: realm,
url: configService.values.OAUTH_URL,
realm: tenantContextHolder.currentTenant,
clientId: configService.values.OAUTH_CLIENT_ID,
},
initOptions: {
@ -40,12 +41,9 @@ function getKeycloakOptions(baseUrl: string, configService: IqserConfigService):
};
}
function configureAutomaticRedirectToLoginScreen(keyCloakService: KeycloakService, configService: IqserConfigService) {
function configureAutomaticRedirectToLoginScreen(keyCloakService: KeycloakService, keycloakStatusService: KeycloakStatusService) {
keyCloakService.getKeycloakInstance().onAuthRefreshError = () => {
window.location.href = keyCloakService.getKeycloakInstance().createLoginUrl({
redirectUri: window.location.href,
idpHint: configService.values.OAUTH_IDP_HINT,
});
keycloakStatusService.createLoginUrlAndExecute();
};
}
@ -53,9 +51,44 @@ export function keycloakInitializer(
keycloakService: KeycloakService,
configService: IqserConfigService,
baseUrl: string,
): () => Promise<void> {
const x = keycloakService.init(getKeycloakOptions(baseUrl, configService));
return () => x.then(() => configureAutomaticRedirectToLoginScreen(keycloakService, configService));
keycloakStatusService: KeycloakStatusService,
tenantContext: TenantContext,
tenantContextHolder: TenantContextHolder,
): () => Promise<boolean> {
const tenantsReady = tenantContext.tenantsReady$.pipe(
filter(t => t),
switchMap(() => {
if (tenantContextHolder.currentTenant) {
const x = keycloakService.init(getKeycloakOptions(baseUrl, tenantContextHolder, configService));
configureAutomaticRedirectToLoginScreen(keycloakService, keycloakStatusService);
keycloakStatusService.updateStatus(KeycloakStatus.PENDING);
return x;
} else {
keycloakStatusService.updateStatus(KeycloakStatus.NOT_ACTIVE);
return of(true);
}
}),
);
return () => firstValueFrom(tenantsReady);
}
export function keycloakStatusInitializer(keycloakService: KeycloakService, keycloakStatusService: KeycloakStatusService) {
const pipe = keycloakStatusService.keycloakStatus$.pipe(
filter(status => status === KeycloakStatus.PENDING || status === KeycloakStatus.NOT_ACTIVE),
switchMap(status => {
if (status === KeycloakStatus.NOT_ACTIVE) {
return of(true);
} else {
return keycloakService.keycloakEvents$.pipe(
filter(event => event.type === KeycloakEventType.OnReady),
tap(() => keycloakStatusService.updateStatus(KeycloakStatus.READY)),
map(() => true),
);
}
}),
);
return () => firstValueFrom(pipe);
}
const components = [NamePipe, InitialsAvatarComponent, UserButtonComponent];
@ -81,11 +114,17 @@ export class IqserUsersModule {
userService,
roleGuard,
IqserAuthGuard,
{
provide: APP_INITIALIZER,
useFactory: keycloakStatusInitializer,
multi: true,
deps: [KeycloakService, KeycloakStatusService],
},
{
provide: APP_INITIALIZER,
useFactory: keycloakInitializer,
multi: true,
deps: [KeycloakService, IqserConfigService, BASE_HREF],
deps: [KeycloakService, IqserConfigService, BASE_HREF, KeycloakStatusService, TenantContext, TenantContextHolder],
},
],
};

View File

@ -16,6 +16,7 @@ import { IqserUser } from '../iqser-user.model';
import { IqserPermissionsService, IqserRolesService } from '../../permissions';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { KeycloakStatusService } from './keycloak-status.service';
@Injectable()
export abstract class IqserUserService<
@ -33,6 +34,7 @@ export abstract class IqserUserService<
protected readonly _configService = inject(IqserConfigService);
protected readonly _keycloakService = inject(KeycloakService);
protected readonly _cacheApiService = inject(CacheApiService);
protected readonly _keycloakStatusService = inject(KeycloakStatusService);
protected readonly _permissionsService = inject(IqserPermissionsService, { optional: true });
protected readonly _rolesService = inject(IqserRolesService, { optional: true });
@ -60,7 +62,7 @@ export abstract class IqserUserService<
try {
await this._keycloakService.loadUserProfile(true);
await this._cacheApiService.wipeCaches();
await this._keycloakService.logout();
await this._keycloakService.logout(this._keycloakStatusService.createLoginUrl());
} catch (e) {
await this.redirectToLogin();
}
@ -68,10 +70,7 @@ export abstract class IqserUserService<
async redirectToLogin() {
await this._cacheApiService.wipeCaches();
window.location.href = this._keycloakService.getKeycloakInstance().createLoginUrl({
redirectUri: window.location.origin + this._baseHref,
idpHint: this._configService.values.OAUTH_IDP_HINT,
});
this._keycloakStatusService.createLoginUrlAndExecute();
}
loadAll() {

View File

@ -0,0 +1,52 @@
import { inject, Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { KeycloakService } from 'keycloak-angular';
import { IqserConfigService } from '../../services';
import { TenantContextHolder } from '../../tenants';
import { BASE_HREF } from '../../utils';
export enum KeycloakStatus {
UNKNOWN = 'UNKNOWN',
PENDING = 'PENDING',
READY = 'READY',
NOT_ACTIVE = 'NOT_ACTIVE',
}
@Injectable({ providedIn: 'root' })
export class KeycloakStatusService {
readonly keycloakStatus$ = new BehaviorSubject<KeycloakStatus>(KeycloakStatus.UNKNOWN);
private readonly _keyCloakService = inject(KeycloakService);
private readonly _configService = inject(IqserConfigService);
private readonly _tenantContextHolder = inject(TenantContextHolder);
private readonly _baseHref = inject(BASE_HREF);
updateStatus(status: KeycloakStatus) {
this.keycloakStatus$.next(status);
}
createLoginUrlAndExecute() {
window.location.href = this._keyCloakService.getKeycloakInstance().createLoginUrl({
redirectUri: this.createLoginUrl(),
idpHint: this._configService.values.OAUTH_IDP_HINT,
});
}
createLoginUrl() {
let url;
if (
this._tenantContextHolder.currentTenant &&
window.location.href.indexOf('/' + this._tenantContextHolder.currentTenant + '/main') > 0
) {
url = window.location.href;
} else {
url = window.location.origin + this._baseHref;
if (this._tenantContextHolder.currentTenant) {
url = url + '/' + this._tenantContextHolder.currentTenant + '/main';
}
}
console.log('Created Url', url, this._baseHref, this._tenantContextHolder.currentTenant);
return url;
}
}

View File

@ -12,6 +12,8 @@ export const BASE_HREF = new InjectionToken<string>('BASE_HREF', {
return baseUrl.substring(0, baseUrl.length - 1);
}
console.log('Base URL:', baseUrl);
return baseUrl;
},
});