diff --git a/src/index.ts b/src/index.ts index bf0a0de..9d7c8c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/lib/permissions/services/permissions-guard.service.ts b/src/lib/permissions/services/permissions-guard.service.ts index e023583..c02444d 100644 --- a/src/lib/permissions/services/permissions-guard.service.ts +++ b/src/lib/permissions/services/permissions-guard.service.ts @@ -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(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}`]); } diff --git a/src/lib/tenants/guards/if-logged-in.guard.ts b/src/lib/tenants/guards/if-logged-in.guard.ts deleted file mode 100644 index 45c390f..0000000 --- a/src/lib/tenants/guards/if-logged-in.guard.ts +++ /dev/null @@ -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; -}; diff --git a/src/lib/tenants/index.ts b/src/lib/tenants/index.ts index dfb088e..501911d 100644 --- a/src/lib/tenants/index.ts +++ b/src/lib/tenants/index.ts @@ -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'; diff --git a/src/lib/tenants/keycloak-initializer.ts b/src/lib/tenants/keycloak-initializer.ts index 87df2c5..b585d88 100644 --- a/src/lib/tenants/keycloak-initializer.ts +++ b/src/lib/tenants/keycloak-initializer.ts @@ -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, }; } diff --git a/src/lib/tenants/services/keycloak-status.service.ts b/src/lib/tenants/services/keycloak-status.service.ts index 7d19173..f5f3e46 100644 --- a/src/lib/tenants/services/keycloak-status.service.ts +++ b/src/lib/tenants/services/keycloak-status.service.ts @@ -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; diff --git a/src/lib/tenants/services/tenants.service.ts b/src/lib/tenants/services/tenants.service.ts index 6036ab3..74707dc 100644 --- a/src/lib/tenants/services/tenants.service.ts +++ b/src/lib/tenants/services/tenants.service.ts @@ -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; -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(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(''); - 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(`/${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]); - } } diff --git a/src/lib/tenants/tenant-select/tenant-select.component.html b/src/lib/tenants/tenant-select/tenant-select.component.html index 03db07a..3945b4e 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.html +++ b/src/lib/tenants/tenant-select/tenant-select.component.html @@ -17,15 +17,14 @@
- {{ stored.tenant.displayName }} + {{ stored.tenantId }} {{ stored.email }}
diff --git a/src/lib/tenants/tenant-select/tenant-select.component.ts b/src/lib/tenants/tenant-select/tenant-select.component.ts index e9df36e..b4d3ddb 100644 --- a/src/lib/tenants/tenant-select/tenant-select.component.ts +++ b/src/lib/tenants/tenant-select/tenant-select.component.ts @@ -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; } } diff --git a/src/lib/tenants/tenants.module.ts b/src/lib/tenants/tenants.module.ts index 2a5aa88..7fedf45 100644 --- a/src/lib/tenants/tenants.module.ts +++ b/src/lib/tenants/tenants.module.ts @@ -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 { @@ -53,14 +51,6 @@ export class TenantsModule { multi: true, useClass: TenantIdResponseInterceptor, }, - { - provide: APP_INITIALIZER, - multi: true, - useFactory() { - const tenantsService = inject(TenantsService); - return () => tenantsService.loadTenants(); - }, - }, ], }; } diff --git a/src/lib/users/guards/has-roles.guard.ts b/src/lib/users/guards/has-roles.guard.ts new file mode 100644 index 0000000..53227eb --- /dev/null +++ b/src/lib/users/guards/has-roles.guard.ts @@ -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; + }; +} diff --git a/src/lib/users/guards/iqser-auth-guard.service.ts b/src/lib/users/guards/iqser-auth-guard.service.ts index fa3971f..9333d6f 100644 --- a/src/lib/users/guards/iqser-auth-guard.service.ts +++ b/src/lib/users/guards/iqser-auth-guard.service.ts @@ -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 { - 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; } } diff --git a/src/lib/users/index.ts b/src/lib/users/index.ts index 4ccf4a5..744b89f 100644 --- a/src/lib/users/index.ts +++ b/src/lib/users/index.ts @@ -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'; diff --git a/src/lib/users/iqser-user.model.ts b/src/lib/users/iqser-user.model.ts index 443dedf..6d93dae 100644 --- a/src/lib/users/iqser-user.model.ts +++ b/src/lib/users/iqser-user.model.ts @@ -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;