diff --git a/bizmatch/package.json b/bizmatch/package.json index c093c93..eac3bef 100644 --- a/bizmatch/package.json +++ b/bizmatch/package.json @@ -37,7 +37,7 @@ "dayjs": "^1.11.11", "express": "^4.18.2", "jwt-decode": "^4.0.0", - "keycloak-js": "^23.0.7", + "keycloak-js": "^24.0.4", "memoize-one": "^6.0.0", "on-change": "^5.0.1", "primeflex": "^3.3.1", diff --git a/bizmatch/src/app/app.config.ts b/bizmatch/src/app/app.config.ts index ca00719..96c388b 100644 --- a/bizmatch/src/app/app.config.ts +++ b/bizmatch/src/app/app.config.ts @@ -6,9 +6,9 @@ import { provideAnimations } from '@angular/platform-browser/animations'; import { environment } from '../environments/environment'; import { routes } from './app.routes'; import { LoadingInterceptor } from './interceptors/loading.interceptor'; +import { KeycloakInitializerService } from './services/keycloak-initializer.service'; import { KeycloakService } from './services/keycloak.service'; import { SelectOptionsService } from './services/select-options.service'; -import { UserService } from './services/user.service'; // provideClientHydration() export const appConfig: ApplicationConfig = { providers: [ @@ -16,9 +16,11 @@ export const appConfig: ApplicationConfig = { { provide: KeycloakService }, { provide: APP_INITIALIZER, - useFactory: initializeKeycloak, + // useFactory: initializeKeycloak, + useFactory: (keycloakInitializer: KeycloakInitializerService) => async () => await keycloakInitializer.initialize(), multi: true, - deps: [KeycloakService], + // deps: [KeycloakService], + deps: [KeycloakInitializerService], }, { provide: APP_INITIALIZER, @@ -40,24 +42,17 @@ export const appConfig: ApplicationConfig = { }), ), provideAnimations(), - - // {provide: LOCALE_ID, useValue: 'en-US' } ], }; -function initUserService(userService: UserService) { - return () => { - //selectOptions.init(); - }; -} function initServices(selectOptions: SelectOptionsService) { - return () => { - selectOptions.init(); + return async () => { + await selectOptions.init(); }; } function initializeKeycloak(keycloak: KeycloakService) { - return () => - keycloak.init({ + return async () => { + const authenticated = await keycloak.init({ config: { url: environment.keycloak.url, realm: environment.keycloak.realm, @@ -68,4 +63,6 @@ function initializeKeycloak(keycloak: KeycloakService) { silentCheckSsoRedirectUri: (window).location.origin + '/assets/silent-check-sso.html', }, }); + console.log(`--->${authenticated}`); + }; } diff --git a/bizmatch/src/app/components/footer/footer.component.html b/bizmatch/src/app/components/footer/footer.component.html index 67d12ba..ac0fe58 100644 --- a/bizmatch/src/app/components/footer/footer.component.html +++ b/bizmatch/src/app/components/footer/footer.component.html @@ -17,9 +17,9 @@
Actions
- Login - Account - Log Out + Login + Account + Log Out
diff --git a/bizmatch/src/app/components/footer/footer.component.ts b/bizmatch/src/app/components/footer/footer.component.ts index f709930..f7a1098 100644 --- a/bizmatch/src/app/components/footer/footer.component.ts +++ b/bizmatch/src/app/components/footer/footer.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { SidebarModule } from 'primeng/sidebar'; -import { UserService } from '../../services/user.service'; +import { KeycloakService } from '../../services/keycloak.service'; import { SharedModule } from '../../shared/shared/shared.module'; @Component({ selector: 'footer', @@ -12,8 +12,10 @@ import { SharedModule } from '../../shared/shared/shared.module'; export class FooterComponent { privacyVisible = false; termsVisible = false; - constructor(public userService: UserService) {} + constructor(public keycloakService: KeycloakService) {} login() { - this.userService.login(window.location.href); + this.keycloakService.login({ + redirectUri: window.location.href, + }); } } diff --git a/bizmatch/src/app/components/header/header.component.html b/bizmatch/src/app/components/header/header.component.html index ec8298f..82510a9 100644 --- a/bizmatch/src/app/components/header/header.component.html +++ b/bizmatch/src/app/components/header/header.component.html @@ -4,7 +4,9 @@ -
Welcome, {{ user.firstName }}
+ @if(user){ +
Welcome, {{ user.firstName }}
+ } diff --git a/bizmatch/src/app/components/header/header.component.ts b/bizmatch/src/app/components/header/header.component.ts index 10534bb..4cb60df 100644 --- a/bizmatch/src/app/components/header/header.component.ts +++ b/bizmatch/src/app/components/header/header.component.ts @@ -10,7 +10,8 @@ import { TabMenuModule } from 'primeng/tabmenu'; import { Observable } from 'rxjs'; import { KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../../environments/environment'; -import { UserService } from '../../services/user.service'; +import { KeycloakService } from '../../services/keycloak.service'; +import { map2User } from '../../utils/utils'; @Component({ selector: 'header', standalone: true, @@ -27,63 +28,65 @@ export class HeaderComponent { public menuItems: MenuItem[]; activeItem; faUserGear = faUserGear; - constructor(public userService: UserService, private router: Router) {} + constructor(public keycloakService: KeycloakService, private router: Router) {} - ngOnInit() { - this.user$ = this.userService.getUserObservable(); - this.user$.subscribe(u => { - this.user = u; - this.menuItems = [ - { - label: 'User Actions', - icon: 'fas fa-cog', - items: [ - { - label: 'Account', - icon: 'pi pi-user', - routerLink: `/account`, - visible: this.isUserLogedIn(), - }, - { - label: 'Create Listing', - icon: 'pi pi-plus-circle', - routerLink: '/createBusinessListing', - visible: this.isUserLogedIn(), - }, - { - label: 'My Listings', - icon: 'pi pi-list', - routerLink: '/myListings', - visible: this.isUserLogedIn(), - }, - { - label: 'My Favorites', - icon: 'pi pi-star', - routerLink: '/myFavorites', - visible: this.isUserLogedIn(), - }, - { - label: 'EMail Us', - icon: 'fa-regular fa-envelope', - routerLink: '/emailUs', - visible: this.isUserLogedIn(), - }, - { - label: 'Logout', - icon: 'fa-solid fa-right-from-bracket', - routerLink: '/logout', - visible: this.isUserLogedIn(), - }, - { - label: 'Login', - icon: 'fa-solid fa-right-from-bracket', - command: () => this.login(), - visible: !this.isUserLogedIn(), - }, - ], - }, - ]; - }); + async ngOnInit() { + const token = await this.keycloakService.getToken(); + this.user = map2User(token); + //this.user$ = this.keycloakService + // this.user$.subscribe(u => { + // this.user = u; + this.menuItems = [ + { + label: 'User Actions', + icon: 'fas fa-cog', + items: [ + { + label: 'Account', + icon: 'pi pi-user', + routerLink: `/account`, + visible: this.keycloakService.isLoggedIn(), + }, + { + label: 'Create Listing', + icon: 'pi pi-plus-circle', + routerLink: '/createBusinessListing', + visible: this.keycloakService.isLoggedIn(), + }, + { + label: 'My Listings', + icon: 'pi pi-list', + routerLink: '/myListings', + visible: this.keycloakService.isLoggedIn(), + }, + { + label: 'My Favorites', + icon: 'pi pi-star', + routerLink: '/myFavorites', + visible: this.keycloakService.isLoggedIn(), + }, + { + label: 'EMail Us', + icon: 'fa-regular fa-envelope', + routerLink: '/emailUs', + visible: this.keycloakService.isLoggedIn(), + }, + { + label: 'Logout', + icon: 'fa-solid fa-right-from-bracket', + routerLink: '/logout', + visible: this.keycloakService.isLoggedIn(), + }, + { + label: 'Login', + icon: 'fa-solid fa-right-from-bracket', + command: () => this.login(), + visible: !this.keycloakService.isLoggedIn(), + }, + ], + }, + ]; + // }); this.tabItems = [ { label: 'Businesses for Sale', @@ -102,12 +105,12 @@ export class HeaderComponent { { label: 'Login', command: () => this.login(), - visible: !this.isUserLogedIn(), + visible: !this.keycloakService.isLoggedIn(), }, { label: 'Register', command: () => this.register(), - visible: !this.isUserLogedIn(), + visible: !this.keycloakService.isLoggedIn(), }, ]; this.activeItem = this.tabItems[0]; @@ -116,13 +119,12 @@ export class HeaderComponent { navigateWithState(dest: string, state: any) { this.router.navigate([dest], { state: state }); } - isUserLogedIn() { - return this.userService?.isLoggedIn(); - } login() { - this.userService.login(window.location.href); + this.keycloakService.login({ + redirectUri: window.location.href, + }); } register() { - this.userService.register(`${window.location.origin}/account`); + this.keycloakService.register({ redirectUri: `${window.location.origin}/account` }); } } diff --git a/bizmatch/src/app/components/logout/logout.component.ts b/bizmatch/src/app/components/logout/logout.component.ts index ddb1cf6..70ba9ee 100644 --- a/bizmatch/src/app/components/logout/logout.component.ts +++ b/bizmatch/src/app/components/logout/logout.component.ts @@ -1,16 +1,17 @@ -import { Component } from '@angular/core'; -import { UserService } from '../../services/user.service'; import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; +import { KeycloakService } from '../../services/keycloak.service'; @Component({ selector: 'logout', standalone: true, - imports: [CommonModule,RouterModule], - template:`` + imports: [CommonModule, RouterModule], + template: ``, }) export class LogoutComponent { - constructor(private userService:UserService){ - userService.logout(); + constructor(public keycloakService: KeycloakService) { + sessionStorage.removeItem('USERID'); + keycloakService.logout(window.location.origin + '/home'); } } diff --git a/bizmatch/src/app/guards/auth.guard.ts b/bizmatch/src/app/guards/auth.guard.ts index 3565e46..12bfbb5 100644 --- a/bizmatch/src/app/guards/auth.guard.ts +++ b/bizmatch/src/app/guards/auth.guard.ts @@ -1,22 +1,30 @@ -import { CanMatchFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import { inject } from '@angular/core'; +import { CanMatchFn, Router, UrlTree } from '@angular/router'; // Services -import { UserService } from '../services/user.service'; - +import { KeycloakInitializerService } from '../services/keycloak-initializer.service'; +import { KeycloakService } from '../services/keycloak.service'; +import { createLogger } from '../utils/utils'; +const logger = createLogger('authGuard'); export const authGuard: CanMatchFn = async (route, segments): Promise => { const router = inject(Router); - const userService = inject(UserService); - - const authenticated: boolean = userService.isLoggedIn(); + const keycloakService = inject(KeycloakService); + const keycloakInitializer = inject(KeycloakInitializerService); + if (!keycloakInitializer.isInitialized()) { + await keycloakInitializer.initialize(); + } + logger.info('###-> calling isLoggedIn()'); + const authenticated = keycloakService.isLoggedIn(); if (!authenticated) { - console.log(window.location.origin) - console.log(window.location.href) - await userService.login(`${window.location.origin}${segments['url']}`); + console.log(window.location.origin); + console.log(window.location.href); + keycloakService.login({ + redirectUri: `${window.location.origin}${segments['url']}`, + }); } // Get the user Keycloak roles and the required from the route - const roles: string[] = userService.getUserRoles();//keycloakService.getUserRoles(true); + const roles: string[] = keycloakService.getUserRoles(true); const requiredRoles = route.data?.['roles']; // Allow the user to proceed if no additional roles are required to access the route @@ -24,15 +32,11 @@ export const authGuard: CanMatchFn = async (route, segments): Promise roles.includes(role)); - // Allow the user to proceed if ONE of the required roles is present - //const authorized = requiredRoles.some((role) => roles.includes(role)); + const authorized = requiredRoles.every(role => roles.includes(role)); if (authorized) { return true; } - // Display my custom HTTP 403 access denied page - return router.createUrlTree(['/access']); -}; \ No newline at end of file + return router.createUrlTree(['/home']); +}; diff --git a/bizmatch/src/app/interceptors/loading.interceptor.ts b/bizmatch/src/app/interceptors/loading.interceptor.ts index a59f504..bcb7f35 100644 --- a/bizmatch/src/app/interceptors/loading.interceptor.ts +++ b/bizmatch/src/app/interceptors/loading.interceptor.ts @@ -1,24 +1,22 @@ -import { HttpEvent, HttpHandler, HttpInterceptor, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; -import { Injectable, inject } from '@angular/core'; +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; import { Observable, tap } from 'rxjs'; import { v4 } from 'uuid'; import { LoadingService } from '../services/loading.service'; @Injectable() export class LoadingInterceptor implements HttpInterceptor { - constructor(private loadingService:LoadingService) { } - intercept(request: HttpRequest, next: HttpHandler): Observable> { - console.log("Intercepting Requests") - const requestId = `HTTP-${v4()}`; - this.loadingService.startLoading(requestId,request.url); + constructor(private loadingService: LoadingService) {} + intercept(request: HttpRequest, next: HttpHandler): Observable> { + const requestId = `HTTP-${v4()}`; + this.loadingService.startLoading(requestId, request.url); - // return next.handle(request); - return next.handle(request).pipe( - tap({ - finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist - // Beachte, dass 'error' und 'complete' hier entfernt wurden, da 'finalize' in allen Fällen aufgerufen wird, - // egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde. - }) - ); - } -} \ No newline at end of file + return next.handle(request).pipe( + tap({ + finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist + // Beachte, dass 'error' und 'complete' hier entfernt wurden, da 'finalize' in allen Fällen aufgerufen wird, + // egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde. + }), + ); + } +} diff --git a/bizmatch/src/app/models/keycloak-event.ts b/bizmatch/src/app/models/keycloak-event.ts index 1773dbc..2018175 100644 --- a/bizmatch/src/app/models/keycloak-event.ts +++ b/bizmatch/src/app/models/keycloak-event.ts @@ -1,52 +1,64 @@ +/** + * @license + * Copyright Mauricio Gemelli Vigolo and contributors. + * + * Use of this source code is governed by a MIT-style license that can be + * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md + */ + +/** + * Keycloak event types, as described at the keycloak-js documentation: + * https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events + */ export enum KeycloakEventType { - /** - * Called if there was an error during authentication. - */ - OnAuthError, - /** - * Called if the user is logged out - * (will only be called if the session status iframe is enabled, or in Cordova mode). - */ - OnAuthLogout, - /** - * Called if there was an error while trying to refresh the token. - */ - OnAuthRefreshError, - /** - * Called when the token is refreshed. - */ - OnAuthRefreshSuccess, - /** - * Called when a user is successfully authenticated. - */ - OnAuthSuccess, - /** - * Called when the adapter is initialized. - */ - OnReady, - /** - * Called when the access token is expired. If a refresh token is available the token - * can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow) - * you can redirect to login screen to obtain a new access token. - */ - OnTokenExpired, - /** - * Called when a AIA has been requested by the application. - */ - OnActionUpdate - } - /** - * Structure of an event triggered by Keycloak, contains it's type - * and arguments (if any). + * Called if there was an error during authentication. */ - export interface KeycloakEvent { - /** - * Event type as described at {@link KeycloakEventType}. - */ - type: KeycloakEventType; - /** - * Arguments from the keycloak-js event function. - */ - args?: unknown; - } \ No newline at end of file + OnAuthError, + /** + * Called if the user is logged out + * (will only be called if the session status iframe is enabled, or in Cordova mode). + */ + OnAuthLogout, + /** + * Called if there was an error while trying to refresh the token. + */ + OnAuthRefreshError, + /** + * Called when the token is refreshed. + */ + OnAuthRefreshSuccess, + /** + * Called when a user is successfully authenticated. + */ + OnAuthSuccess, + /** + * Called when the adapter is initialized. + */ + OnReady, + /** + * Called when the access token is expired. If a refresh token is available the token + * can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow) + * you can redirect to login screen to obtain a new access token. + */ + OnTokenExpired, + /** + * Called when a AIA has been requested by the application. + */ + OnActionUpdate, +} + +/** + * Structure of an event triggered by Keycloak, contains it's type + * and arguments (if any). + */ +export interface KeycloakEvent { + /** + * Event type as described at {@link KeycloakEventType}. + */ + type: KeycloakEventType; + /** + * Arguments from the keycloak-js event function. + */ + args?: unknown; +} diff --git a/bizmatch/src/app/models/keycloak-options.ts b/bizmatch/src/app/models/keycloak-options.ts index 04450b1..2cd06f2 100644 --- a/bizmatch/src/app/models/keycloak-options.ts +++ b/bizmatch/src/app/models/keycloak-options.ts @@ -11,14 +11,7 @@ import { HttpRequest } from '@angular/common/http'; /** * HTTP Methods */ -export type HttpMethods = - | 'GET' - | 'POST' - | 'PUT' - | 'DELETE' - | 'OPTIONS' - | 'HEAD' - | 'PATCH'; +export type HttpMethods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; /** * ExcludedUrl type may be used to specify the url and the HTTP method that @@ -139,4 +132,4 @@ export interface KeycloakOptions { * The default is a function that always returns `true`. */ shouldUpdateToken?: (request: HttpRequest) => boolean; -} \ No newline at end of file +} diff --git a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts index 5944ac2..6309e64 100644 --- a/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-business-listing/details-business-listing.component.ts @@ -9,6 +9,7 @@ import { BusinessListing, User } from '../../../../../../bizmatch-server/src/mod import { KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../../../environments/environment'; import { HistoryService } from '../../../services/history.service'; +import { KeycloakService } from '../../../services/keycloak.service'; import { ListingsService } from '../../../services/listings.service'; import { MailService } from '../../../services/mail.service'; import { SelectOptionsService } from '../../../services/select-options.service'; @@ -64,6 +65,7 @@ export class DetailsBusinessListingComponent { private messageService: MessageService, private sanitizer: DomSanitizer, public historyService: HistoryService, + public keycloakService: KeycloakService, ) { this.router.events.subscribe(event => { if (event instanceof NavigationEnd) { @@ -71,9 +73,7 @@ export class DetailsBusinessListingComponent { } }); this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl }; - this.userService.getUserObservable().subscribe(user => { - this.user = user; - }); + this.user; this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler); } @@ -84,7 +84,7 @@ export class DetailsBusinessListingComponent { } isAdmin() { - return this.userService.hasAdminRole(); + return this.keycloakService.getUserRoles(true).includes('ADMIN'); } async mail() { this.mailinfo.email = this.listingUser.email; diff --git a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts index f980489..3919dbc 100644 --- a/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts +++ b/bizmatch/src/app/pages/details/details-commercial-property-listing/details-commercial-property-listing.component.ts @@ -9,12 +9,13 @@ import { CommercialPropertyListing, User } from '../../../../../../bizmatch-serv import { ErrorResponse, KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../../../environments/environment'; import { HistoryService } from '../../../services/history.service'; +import { KeycloakService } from '../../../services/keycloak.service'; import { ListingsService } from '../../../services/listings.service'; import { MailService } from '../../../services/mail.service'; import { SelectOptionsService } from '../../../services/select-options.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; -import { getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils'; +import { getCriteriaStateObject, getSessionStorageHandler, map2User } from '../../../utils/utils'; @Component({ selector: 'app-details-commercial-property-listing', @@ -65,22 +66,23 @@ export class DetailsCommercialPropertyListingComponent { private messageService: MessageService, private sanitizer: DomSanitizer, public historyService: HistoryService, + public keycloakService: KeycloakService, ) { this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl }; - this.userService.getUserObservable().subscribe(user => { - this.user = user; - }); + this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler); } async ngOnInit() { + const token = await this.keycloakService.getToken(); + this.user = map2User(token); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty')); this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id); this.listingUser = await this.userService.getById(this.listing.userId); this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description); } isAdmin() { - return this.userService.hasAdminRole(); + return this.keycloakService.getUserRoles(true).includes('ADMIN'); } async mail() { this.mailinfo.email = this.listingUser.email; diff --git a/bizmatch/src/app/pages/details/details-user/details-user.component.html b/bizmatch/src/app/pages/details/details-user/details-user.component.html index 8081de7..6877e4c 100644 --- a/bizmatch/src/app/pages/details/details-user/details-user.component.html +++ b/bizmatch/src/app/pages/details/details-user/details-user.component.html @@ -138,7 +138,7 @@ - @if( user?.email===(user$| async)?.email || isAdmin()){ + @if( user?.email===keycloakUser?.email || isAdmin()){ } diff --git a/bizmatch/src/app/pages/details/details-user/details-user.component.ts b/bizmatch/src/app/pages/details/details-user/details-user.component.ts index 4765fef..fe1a3d7 100644 --- a/bizmatch/src/app/pages/details/details-user/details-user.component.ts +++ b/bizmatch/src/app/pages/details/details-user/details-user.component.ts @@ -9,10 +9,12 @@ import { KeycloakUser, ListingCriteria } from '../../../../../../bizmatch-server import { environment } from '../../../../environments/environment'; import { HistoryService } from '../../../services/history.service'; import { ImageService } from '../../../services/image.service'; +import { KeycloakService } from '../../../services/keycloak.service'; import { ListingsService } from '../../../services/listings.service'; import { SelectOptionsService } from '../../../services/select-options.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; +import { map2User } from '../../../utils/utils'; @Component({ selector: 'app-details-user', @@ -26,6 +28,7 @@ export class DetailsUserComponent { private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; user: User; user$: Observable; + keycloakUser: KeycloakUser; environment = environment; criteria: ListingCriteria; businessListings: BusinessListing[]; @@ -44,6 +47,7 @@ export class DetailsUserComponent { private sanitizer: DomSanitizer, private imageService: ImageService, public historyService: HistoryService, + public keycloakService: KeycloakService, ) {} async ngOnInit() { @@ -53,12 +57,14 @@ export class DetailsUserComponent { // Zuweisen der Ergebnisse zu den Member-Variablen der Klasse this.businessListings = results[0]; this.commercialPropListings = results[1]; - this.user$ = this.userService.getUserObservable(); + //this.user$ = this.userService.getUserObservable(); + const token = await this.keycloakService.getToken(); + this.keycloakUser = map2User(token); this.companyOverview = this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview); this.offeredServices = this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices); } isAdmin() { - return this.userService.hasAdminRole(); + return this.keycloakService.getUserRoles(true).includes('ADMIN'); } } diff --git a/bizmatch/src/app/pages/home/home.component.html b/bizmatch/src/app/pages/home/home.component.html index 26ce499..82b0004 100644 --- a/bizmatch/src/app/pages/home/home.component.html +++ b/bizmatch/src/app/pages/home/home.component.html @@ -5,7 +5,7 @@ diff --git a/bizmatch/src/app/pages/home/home.component.ts b/bizmatch/src/app/pages/home/home.component.ts index 92db8bc..e43f34e 100644 --- a/bizmatch/src/app/pages/home/home.component.ts +++ b/bizmatch/src/app/pages/home/home.component.ts @@ -8,11 +8,10 @@ import { CheckboxModule } from 'primeng/checkbox'; import { DropdownModule } from 'primeng/dropdown'; import { InputTextModule } from 'primeng/inputtext'; import { StyleClassModule } from 'primeng/styleclass'; -import { Observable } from 'rxjs'; -import { KeycloakUser, ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model'; +import { ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model'; +import { KeycloakService } from '../../services/keycloak.service'; import { ListingsService } from '../../services/listings.service'; import { SelectOptionsService } from '../../services/select-options.service'; -import { UserService } from '../../services/user.service'; import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../utils/utils'; @Component({ selector: 'app-home', @@ -27,14 +26,12 @@ export class HomeComponent { maxPrice: string; minPrice: string; criteria: ListingCriteria; - user$: Observable; states = []; - public constructor(private router: Router, private activatedRoute: ActivatedRoute, public selectOptions: SelectOptionsService, public userService: UserService, private listingsService: ListingsService) { + public constructor(private router: Router, private activatedRoute: ActivatedRoute, public selectOptions: SelectOptionsService, public keycloakService: KeycloakService, private listingsService: ListingsService) { this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler); resetCriteria(this.criteria); } async ngOnInit() { - this.user$ = this.userService.getUserObservable(); if (this.activeTabAction === 'business' || this.activeTabAction === 'commercialProperty') { const statesResult = await this.listingsService.getAllStates(this.activeTabAction); this.states = statesResult.map(s => s.state).map(ls => ({ name: this.selectOptions.getState(ls as string), value: ls })); @@ -57,9 +54,11 @@ export class HomeComponent { } login() { - this.userService.login(window.location.href); + this.keycloakService.login({ + redirectUri: window.location.href, + }); } register() { - this.userService.register(`${window.location.origin}/account`); + this.keycloakService.register({ redirectUri: `${window.location.origin}/account` }); } } diff --git a/bizmatch/src/app/pages/menu-account/menu-account.component.html b/bizmatch/src/app/pages/menu-account/menu-account.component.html index 9f31a9d..42a7cb6 100644 --- a/bizmatch/src/app/pages/menu-account/menu-account.component.html +++ b/bizmatch/src/app/pages/menu-account/menu-account.component.html @@ -47,12 +47,7 @@
  • - + diff --git a/bizmatch/src/app/pages/menu-account/menu-account.component.ts b/bizmatch/src/app/pages/menu-account/menu-account.component.ts index ab1a4d3..b2ad2fa 100644 --- a/bizmatch/src/app/pages/menu-account/menu-account.component.ts +++ b/bizmatch/src/app/pages/menu-account/menu-account.component.ts @@ -1,37 +1,27 @@ -import { Component } from '@angular/core'; -import { ButtonModule } from 'primeng/button'; -import { CheckboxModule } from 'primeng/checkbox'; -import { InputTextModule } from 'primeng/inputtext'; -import { StyleClassModule } from 'primeng/styleclass'; -import { SelectOptionsService } from '../../services/select-options.service'; -import { DropdownModule } from 'primeng/dropdown'; -import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; -import { ToggleButtonModule } from 'primeng/togglebutton'; -import { TagModule } from 'primeng/tag'; -import data from '../../../assets/data/listings.json'; -import { ActivatedRoute, NavigationEnd, Router, RouterModule } from '@angular/router'; -import { InputTextareaModule } from 'primeng/inputtextarea'; -import { ChipModule } from 'primeng/chip'; -import { DividerModule } from 'primeng/divider'; -import { RippleModule } from 'primeng/ripple'; +import { Component } from '@angular/core'; +import { NavigationEnd, Router, RouterModule } from '@angular/router'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { faEnvelope } from '@fortawesome/free-regular-svg-icons'; import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { UserService } from '../../services/user.service'; +import { ButtonModule } from 'primeng/button'; +import { DividerModule } from 'primeng/divider'; +import { RippleModule } from 'primeng/ripple'; +import { StyleClassModule } from 'primeng/styleclass'; +import { KeycloakService } from '../../services/keycloak.service'; @Component({ selector: 'menu-account', standalone: true, - imports: [CommonModule, StyleClassModule, ButtonModule, DividerModule, RouterModule, RippleModule, FontAwesomeModule ], + imports: [CommonModule, StyleClassModule, ButtonModule, DividerModule, RouterModule, RippleModule, FontAwesomeModule], templateUrl: './menu-account.component.html', - styleUrl: './menu-account.component.scss' + styleUrl: './menu-account.component.scss', }) export class MenuAccountComponent { activeLink: string; - faEnvelope=faEnvelope; - faRightFromBracket=faRightFromBracket; - constructor(private router: Router,public userService:UserService) { + faEnvelope = faEnvelope; + faRightFromBracket = faRightFromBracket; + constructor(private router: Router, public keycloakService: KeycloakService) { // Abonniere Router-Events, um den aktiven Link zu ermitteln this.router.events.subscribe(event => { if (event instanceof NavigationEnd) { @@ -39,4 +29,8 @@ export class MenuAccountComponent { } }); } + logout() { + sessionStorage.removeItem('USERID'); + this.keycloakService.logout(window.location.origin + '/home'); + } } diff --git a/bizmatch/src/app/pages/pricing/pricing.component.ts b/bizmatch/src/app/pages/pricing/pricing.component.ts index 53162b2..946b18f 100644 --- a/bizmatch/src/app/pages/pricing/pricing.component.ts +++ b/bizmatch/src/app/pages/pricing/pricing.component.ts @@ -1,17 +1,17 @@ import { Component } from '@angular/core'; +import { KeycloakService } from '../../services/keycloak.service'; import { SharedModule } from '../../shared/shared/shared.module'; -import { UserService } from '../../services/user.service'; @Component({ selector: 'app-pricing', standalone: true, imports: [SharedModule], templateUrl: './pricing.component.html', - styleUrl: './pricing.component.scss' + styleUrl: './pricing.component.scss', }) export class PricingComponent { - constructor(private userService:UserService){} - register(){ - this.userService.register(`${window.location.origin}/account`); + constructor(public keycloakService: KeycloakService) {} + register() { + this.keycloakService.register({ redirectUri: `${window.location.origin}/account` }); } } diff --git a/bizmatch/src/app/pages/subscription/account/account.component.ts b/bizmatch/src/app/pages/subscription/account/account.component.ts index 00f87e7..4f75556 100644 --- a/bizmatch/src/app/pages/subscription/account/account.component.ts +++ b/bizmatch/src/app/pages/subscription/account/account.component.ts @@ -17,11 +17,13 @@ import { environment } from '../../../../environments/environment'; import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component'; import { GeoService } from '../../../services/geo.service'; import { ImageService } from '../../../services/image.service'; +import { KeycloakService } from '../../../services/keycloak.service'; import { LoadingService } from '../../../services/loading.service'; import { SelectOptionsService } from '../../../services/select-options.service'; import { SubscriptionsService } from '../../../services/subscriptions.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; +import { map2User } from '../../../utils/utils'; import { TOOLBAR_OPTIONS } from '../../utils/defaults'; @Component({ selector: 'app-account', @@ -60,12 +62,14 @@ export class AccountComponent { public dialogService: DialogService, private confirmationService: ConfirmationService, private imageService: ImageService, + private keycloakService: KeycloakService, ) {} async ngOnInit() { if (this.id) { this.user = await this.userService.getById(this.id); } else { - const keycloakUser = this.userService.getKeycloakUser(); + const token = await this.keycloakService.getToken(); + const keycloakUser = map2User(token); const email = keycloakUser.email; try { this.user = await this.userService.getByMail(email); diff --git a/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.ts b/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.ts index 5b13281..67f3b18 100644 --- a/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.ts +++ b/bizmatch/src/app/pages/subscription/edit-business-listing/edit-business-listing.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { lastValueFrom } from 'rxjs'; import { ListingsService } from '../../../services/listings.service'; import { SelectOptionsService } from '../../../services/select-options.service'; -import { createDefaultBusinessListing, routeListingWithState } from '../../../utils/utils'; +import { createDefaultBusinessListing, map2User, routeListingWithState } from '../../../utils/utils'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; @@ -23,6 +23,7 @@ import { InputNumberModule } from '../../../components/inputnumber/inputnumber.c import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe'; import { GeoService } from '../../../services/geo.service'; import { ImageService } from '../../../services/image.service'; +import { KeycloakService } from '../../../services/keycloak.service'; import { LoadingService } from '../../../services/loading.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; @@ -99,6 +100,7 @@ export class EditBusinessListingComponent { public dialogService: DialogService, private confirmationService: ConfirmationService, private route: ActivatedRoute, + private keycloakService: KeycloakService, ) { this.router.events.subscribe(event => { if (event instanceof NavigationEnd) { @@ -115,11 +117,13 @@ export class EditBusinessListingComponent { }); } async ngOnInit() { + const token = await this.keycloakService.getToken(); + const keycloakUser = map2User(token); if (this.mode === 'edit') { this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business')); } else { this.listing = createDefaultBusinessListing(); - this.listing.userId = await this.userService.getId(); + this.listing.userId = await this.userService.getId(keycloakUser.email); if (this.data) { this.listing.title = this.data?.title; this.listing.description = this.data?.description; diff --git a/bizmatch/src/app/pages/subscription/edit-commercial-property-listing/edit-commercial-property-listing.component.ts b/bizmatch/src/app/pages/subscription/edit-commercial-property-listing/edit-commercial-property-listing.component.ts index b781184..aac2101 100644 --- a/bizmatch/src/app/pages/subscription/edit-commercial-property-listing/edit-commercial-property-listing.component.ts +++ b/bizmatch/src/app/pages/subscription/edit-commercial-property-listing/edit-commercial-property-listing.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { lastValueFrom } from 'rxjs'; import { ListingsService } from '../../../services/listings.service'; import { SelectOptionsService } from '../../../services/select-options.service'; -import { createDefaultCommercialPropertyListing, routeListingWithState } from '../../../utils/utils'; +import { createDefaultCommercialPropertyListing, map2User, routeListingWithState } from '../../../utils/utils'; import { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; import { HttpEventType } from '@angular/common/http'; @@ -26,6 +26,7 @@ import { InputNumberModule } from '../../../components/inputnumber/inputnumber.c import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe'; import { GeoService } from '../../../services/geo.service'; import { ImageService } from '../../../services/image.service'; +import { KeycloakService } from '../../../services/keycloak.service'; import { LoadingService } from '../../../services/loading.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; @@ -106,6 +107,7 @@ export class EditCommercialPropertyListingComponent { public dialogService: DialogService, private confirmationService: ConfirmationService, private route: ActivatedRoute, + private keycloakService: KeycloakService, ) { // Abonniere Router-Events, um den aktiven Link zu ermitteln this.router.events.subscribe(event => { @@ -123,11 +125,13 @@ export class EditCommercialPropertyListingComponent { }); } async ngOnInit() { + const token = await this.keycloakService.getToken(); + const keycloakUser = map2User(token); if (this.mode === 'edit') { this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty')); } else { this.listing = createDefaultCommercialPropertyListing(); - this.listing.userId = await this.userService.getId(); + this.listing.userId = await this.userService.getId(keycloakUser.email); this.listing.imagePath = uuidv4(); if (this.data) { this.listing.title = this.data?.title; diff --git a/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts b/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts index 578328d..369f0e9 100644 --- a/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts +++ b/bizmatch/src/app/pages/subscription/favorites/favorites.component.ts @@ -1,9 +1,10 @@ import { Component } from '@angular/core'; import { KeycloakUser, ListingType } from '../../../../../../bizmatch-server/src/models/main.model'; +import { KeycloakService } from '../../../services/keycloak.service'; import { ListingsService } from '../../../services/listings.service'; import { SelectOptionsService } from '../../../services/select-options.service'; -import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; +import { map2User } from '../../../utils/utils'; import { MenuAccountComponent } from '../../menu-account/menu-account.component'; @Component({ @@ -17,11 +18,10 @@ export class FavoritesComponent { user: KeycloakUser; listings: Array = []; //= dataListings as unknown as Array; favorites: Array; - constructor(public userService: UserService, private listingsService: ListingsService, public selectOptions: SelectOptionsService) { - this.user = this.userService.getKeycloakUser(); - } + constructor(public keycloakService: KeycloakService, private listingsService: ListingsService, public selectOptions: SelectOptionsService) {} async ngOnInit() { - // this.listings=await lastValueFrom(this.listingsService.getAllListings()); + const token = await this.keycloakService.getToken(); + this.user = map2User(token); this.favorites = this.listings.filter(l => l.favoritesForUser?.includes(this.user.id)); } } diff --git a/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.ts b/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.ts index 8459067..fb3aed9 100644 --- a/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.ts +++ b/bizmatch/src/app/pages/subscription/my-listing/my-listing.component.ts @@ -2,10 +2,12 @@ import { ChangeDetectorRef, Component } from '@angular/core'; import { ConfirmationService, MessageService } from 'primeng/api'; import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model'; +import { KeycloakService } from '../../../services/keycloak.service'; import { ListingsService } from '../../../services/listings.service'; import { SelectOptionsService } from '../../../services/select-options.service'; import { UserService } from '../../../services/user.service'; import { SharedModule } from '../../../shared/shared/shared.module'; +import { map2User } from '../../../utils/utils'; import { MenuAccountComponent } from '../../menu-account/menu-account.component'; @Component({ selector: 'app-my-listing', @@ -21,6 +23,7 @@ export class MyListingComponent { user: User; constructor( public userService: UserService, + public keycloakService: KeycloakService, private listingsService: ListingsService, private cdRef: ChangeDetectorRef, public selectOptions: SelectOptionsService, @@ -28,7 +31,9 @@ export class MyListingComponent { private messageService: MessageService, ) {} async ngOnInit() { - const keycloakUser = this.userService.getKeycloakUser(); + // const keycloakUser = this.userService.getKeycloakUser(); + const token = await this.keycloakService.getToken(); + const keycloakUser = map2User(token); const email = keycloakUser.email; this.user = await this.userService.getByMail(email); const result = await Promise.all([await this.listingsService.getListingByUserId(this.user.id, 'business'), await this.listingsService.getListingByUserId(this.user.id, 'commercialProperty')]); diff --git a/bizmatch/src/app/resolvers/auth.resolver.ts b/bizmatch/src/app/resolvers/auth.resolver.ts new file mode 100644 index 0000000..09f3da9 --- /dev/null +++ b/bizmatch/src/app/resolvers/auth.resolver.ts @@ -0,0 +1,15 @@ +import { inject } from '@angular/core'; +import { ResolveFn } from '@angular/router'; +import { KeycloakService } from '../services/keycloak.service'; + +export const authResolver: ResolveFn = async (route, state) => { + const keycloakService: KeycloakService = inject(KeycloakService); + + if (!keycloakService.isLoggedIn()) { + await keycloakService.login({ + redirectUri: window.location.href, + }); + } + + return true; +}; diff --git a/bizmatch/src/app/services/keycloak-initializer.service.ts b/bizmatch/src/app/services/keycloak-initializer.service.ts new file mode 100644 index 0000000..70900bf --- /dev/null +++ b/bizmatch/src/app/services/keycloak-initializer.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { environment } from '../../environments/environment'; +import { createLogger } from '../utils/utils'; +import { KeycloakService } from './keycloak.service'; +const logger = createLogger('KeycloakInitializerService'); +@Injectable({ providedIn: 'root' }) +export class KeycloakInitializerService { + private initialized = false; + + constructor(private keycloakService: KeycloakService) {} + + async initialize(): Promise { + if (this.initialized) { + return; + } + + const authenticated = await this.keycloakService.init({ + config: { + url: environment.keycloak.url, + realm: environment.keycloak.realm, + clientId: environment.keycloak.clientId, + }, + initOptions: { + onLoad: 'check-sso', + silentCheckSsoRedirectUri: (window).location.origin + '/assets/silent-check-sso.html', + }, + }); + + logger.info(`--->${authenticated}`); + + this.initialized = true; + } + + isInitialized(): boolean { + return this.initialized; + } +} diff --git a/bizmatch/src/app/services/keycloak.service.ts b/bizmatch/src/app/services/keycloak.service.ts index 222879b..f26bc0c 100644 --- a/bizmatch/src/app/services/keycloak.service.ts +++ b/bizmatch/src/app/services/keycloak.service.ts @@ -6,16 +6,14 @@ * found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md */ -import { Injectable } from '@angular/core'; import { HttpHeaders, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import Keycloak from 'keycloak-js'; import { Subject, from } from 'rxjs'; import { map } from 'rxjs/operators'; -import Keycloak from 'keycloak-js'; - - -import { ExcludedUrl, ExcludedUrlRegex, KeycloakOptions } from '../models/keycloak-options'; import { KeycloakEvent, KeycloakEventType } from '../models/keycloak-event'; +import { ExcludedUrl, ExcludedUrlRegex, KeycloakOptions } from '../models/keycloak-options'; /** * Service to expose existent methods from the Keycloak JS adapter, adding new @@ -64,8 +62,7 @@ export class KeycloakService { /** * Observer for the keycloak events */ - private _keycloakEvents$: Subject = - new Subject(); + private _keycloakEvents$: Subject = new Subject(); /** * The amount of required time remaining before expiry of the token before the token will be refreshed. */ @@ -87,10 +84,10 @@ export class KeycloakService { * argument if the source function provides any. */ private bindsKeycloakEvents(): void { - this._instance.onAuthError = (errorData) => { + this._instance.onAuthError = errorData => { this._keycloakEvents$.next({ args: errorData, - type: KeycloakEventType.OnAuthError + type: KeycloakEventType.OnAuthError, }); }; @@ -100,13 +97,13 @@ export class KeycloakService { this._instance.onAuthRefreshSuccess = () => { this._keycloakEvents$.next({ - type: KeycloakEventType.OnAuthRefreshSuccess + type: KeycloakEventType.OnAuthRefreshSuccess, }); }; this._instance.onAuthRefreshError = () => { this._keycloakEvents$.next({ - type: KeycloakEventType.OnAuthRefreshError + type: KeycloakEventType.OnAuthRefreshError, }); }; @@ -116,21 +113,21 @@ export class KeycloakService { this._instance.onTokenExpired = () => { this._keycloakEvents$.next({ - type: KeycloakEventType.OnTokenExpired + type: KeycloakEventType.OnTokenExpired, }); }; - this._instance.onActionUpdate = (state) => { + this._instance.onActionUpdate = state => { this._keycloakEvents$.next({ args: state, - type: KeycloakEventType.OnActionUpdate + type: KeycloakEventType.OnActionUpdate, }); }; - this._instance.onReady = (authenticated) => { + this._instance.onReady = authenticated => { this._keycloakEvents$.next({ args: authenticated, - type: KeycloakEventType.OnReady + type: KeycloakEventType.OnReady, }); }; } @@ -142,9 +139,7 @@ export class KeycloakService { * @param bearerExcludedUrls array of strings or ExcludedUrl that includes * the url and HttpMethod. */ - private loadExcludedUrls( - bearerExcludedUrls: (string | ExcludedUrl)[] - ): ExcludedUrlRegex[] { + private loadExcludedUrls(bearerExcludedUrls: (string | ExcludedUrl)[]): ExcludedUrlRegex[] { const excludedUrls: ExcludedUrlRegex[] = []; for (const item of bearerExcludedUrls) { let excludedUrl: ExcludedUrlRegex; @@ -153,7 +148,7 @@ export class KeycloakService { } else { excludedUrl = { urlPattern: new RegExp(item.url, 'i'), - httpMethods: item.httpMethods + httpMethods: item.httpMethods, }; } excludedUrls.push(excludedUrl); @@ -175,7 +170,7 @@ export class KeycloakService { initOptions, updateMinValidity = 20, shouldAddToken = () => true, - shouldUpdateToken = () => true + shouldUpdateToken = () => true, }: KeycloakOptions): void { this._enableBearerInterceptor = enableBearerInterceptor; this._loadUserProfileAtStartUp = loadUserProfileAtStartUp; @@ -285,7 +280,7 @@ export class KeycloakService { */ public async logout(redirectUri?: string) { const options = { - redirectUri + redirectUri, }; await this._instance.logout(options); @@ -302,9 +297,7 @@ export class KeycloakService { * @returns * A void Promise if the register flow was successful. */ - public async register( - options: Keycloak.KeycloakLoginOptions = { action: 'register' } - ) { + public async register(options: Keycloak.KeycloakLoginOptions = { action: 'register' }) { await this._instance.register(options); } @@ -345,7 +338,7 @@ export class KeycloakService { let roles: string[] = []; if (this._instance.resourceAccess) { - Object.keys(this._instance.resourceAccess).forEach((key) => { + Object.keys(this._instance.resourceAccess).forEach(key => { if (resource && resource !== key) { return; } @@ -407,9 +400,7 @@ export class KeycloakService { // is not implemented, avoiding the redirect loop. if (this._silentRefresh) { if (this.isTokenExpired()) { - throw new Error( - 'Failed to refresh the token, or the session is expired' - ); + throw new Error('Failed to refresh the token, or the session is expired'); } return true; @@ -442,16 +433,14 @@ export class KeycloakService { } if (!this._instance.authenticated) { - throw new Error( - 'The user profile was not loaded as the user is not logged in.' - ); + throw new Error('The user profile was not loaded as the user is not logged in.'); } return (this._userProfile = await this._instance.loadUserProfile()); } /** - * Returns the authenticated token, calling updateToken to get a refreshed one if necessary. + * Returns the authenticated token. */ public async getToken() { return this._instance.token; @@ -491,16 +480,7 @@ export class KeycloakService { * An observable with with the HTTP Authorization header and the current token. */ public addTokenToHeader(headers: HttpHeaders = new HttpHeaders()) { - return from(this.getToken()).pipe( - map((token) => - token - ? headers.set( - this._authorizationHeaderName, - this._bearerPrefix + token - ) - : headers - ) - ); + return from(this.getToken()).pipe(map(token => (token ? headers.set(this._authorizationHeaderName, this._bearerPrefix + token) : headers))); } /** @@ -558,4 +538,4 @@ export class KeycloakService { get keycloakEvents$(): Subject { return this._keycloakEvents$; } -} \ No newline at end of file +} diff --git a/bizmatch/src/app/services/user.service.ts b/bizmatch/src/app/services/user.service.ts index dd3b75f..4578807 100644 --- a/bizmatch/src/app/services/user.service.ts +++ b/bizmatch/src/app/services/user.service.ts @@ -1,107 +1,18 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable, Signal, computed, effect, signal } from '@angular/core'; -import { jwtDecode } from 'jwt-decode'; -import { Observable, distinctUntilChanged, filter, from, lastValueFrom, map } from 'rxjs'; +import { Injectable } from '@angular/core'; +import { lastValueFrom } from 'rxjs'; import urlcat from 'urlcat'; import { User } from '../../../../bizmatch-server/src/models/db.model'; -import { JwtToken, KeycloakUser, ListingCriteria, ResponseUsersArray, StatesResult } from '../../../../bizmatch-server/src/models/main.model'; +import { ListingCriteria, ResponseUsersArray, StatesResult } from '../../../../bizmatch-server/src/models/main.model'; import { environment } from '../../environments/environment'; -import { KeycloakService } from './keycloak.service'; @Injectable({ providedIn: 'root', }) export class UserService { private apiBaseUrl = environment.apiBaseUrl; - // ----------------------------- - // Keycloak services - // ----------------------------- - private user$ = new Observable(); - private user: KeycloakUser; - public $isLoggedIn: Signal; - constructor(public keycloak: KeycloakService, private http: HttpClient) { - this.user$ = from(this.keycloak.getToken()).pipe( - filter(t => !!t), - distinctUntilChanged(), - map(t => this.map2User(t)), - // tap(u => { - // logger.info('Logged in user:', u); - // this.analyticsService.identify(u); - // }), - ); - this.$isLoggedIn = signal(false); - this.$isLoggedIn = computed(() => { - return keycloak.isLoggedIn(); - }); - effect(async () => { - if (this.$isLoggedIn()) { - this.updateTokenDetails(); - } else { - this.user = null; - } - }); - } - - private async refreshToken(): Promise { - try { - await this.keycloak.updateToken(10); // Versuche, den Token zu erneuern - await this.updateTokenDetails(); // Aktualisiere den Token und seine Details - } catch (error) { - console.error('Fehler beim Token-Refresh', error); - } - } - private async updateTokenDetails(): Promise { - const token = await this.keycloak.getToken(); - this.user = this.map2User(token); - } - - private map2User(jwt: string): KeycloakUser { - const token = jwtDecode(jwt); - return { - id: token.user_id, - firstName: token.given_name, - lastName: token.family_name, - email: token.email, - }; - } - - isLoggedIn(): boolean { - return this.$isLoggedIn(); - } - getKeycloakUser(): KeycloakUser { - return this.user; - } - getUserObservable(): Observable { - return this.user$; - } - async getId(): Promise { - if (sessionStorage.getItem('USERID')) { - return sessionStorage.getItem('USERID'); - } else { - const user = await this.getByMail(this.user.email); - sessionStorage.setItem('USERID', user.id); - return user.id; - } - } - logout() { - sessionStorage.removeItem('USERID'); - this.keycloak.logout(window.location.origin + '/home'); - } - async login(url: string) { - await this.keycloak.login({ - redirectUri: url, - }); - } - getUserRoles() { - return this.keycloak.getUserRoles(true); - } - hasAdminRole() { - return this.keycloak.getUserRoles(true).includes('ADMIN'); - } - register(url: string) { - this.keycloak.register({ redirectUri: url }); - } + constructor(private http: HttpClient) {} // ----------------------------- // DB services @@ -122,4 +33,13 @@ export class UserService { async getAllStates(): Promise { return await lastValueFrom(this.http.get(`${this.apiBaseUrl}/bizmatch/user/states/all`)); } + async getId(email: string): Promise { + if (sessionStorage.getItem('USERID')) { + return sessionStorage.getItem('USERID'); + } else { + const user = await this.getByMail(email); + sessionStorage.setItem('USERID', user.id); + return user.id; + } + } } diff --git a/bizmatch/src/app/utils/utils.ts b/bizmatch/src/app/utils/utils.ts index aabf082..7ac9e4d 100644 --- a/bizmatch/src/app/utils/utils.ts +++ b/bizmatch/src/app/utils/utils.ts @@ -1,7 +1,8 @@ import { Router } from '@angular/router'; import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan'; +import { jwtDecode } from 'jwt-decode'; import { BusinessListing, CommercialPropertyListing } from '../../../../bizmatch-server/src/models/db.model'; -import { ListingCriteria } from '../../../../bizmatch-server/src/models/main.model'; +import { JwtToken, KeycloakUser, ListingCriteria } from '../../../../bizmatch-server/src/models/main.model'; export function createDefaultCommercialPropertyListing(): CommercialPropertyListing { return { @@ -116,3 +117,16 @@ export function resetCriteria(criteria: ListingCriteria) { criteria.title = null; criteria.name = null; } +export function map2User(jwt: string): KeycloakUser { + if (jwt) { + const token = jwtDecode(jwt); + return { + id: token.user_id, + firstName: token.given_name, + lastName: token.family_name, + email: token.email, + }; + } else { + return null; + } +}