new initialization process, keycloak update 24.0.4

This commit is contained in:
Andreas Knuth 2024-05-20 15:54:01 -05:00
parent 747435bfba
commit dc9adb151d
30 changed files with 379 additions and 389 deletions

View File

@ -37,7 +37,7 @@
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"express": "^4.18.2", "express": "^4.18.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"keycloak-js": "^23.0.7", "keycloak-js": "^24.0.4",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
"on-change": "^5.0.1", "on-change": "^5.0.1",
"primeflex": "^3.3.1", "primeflex": "^3.3.1",

View File

@ -6,9 +6,9 @@ import { provideAnimations } from '@angular/platform-browser/animations';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { LoadingInterceptor } from './interceptors/loading.interceptor'; import { LoadingInterceptor } from './interceptors/loading.interceptor';
import { KeycloakInitializerService } from './services/keycloak-initializer.service';
import { KeycloakService } from './services/keycloak.service'; import { KeycloakService } from './services/keycloak.service';
import { SelectOptionsService } from './services/select-options.service'; import { SelectOptionsService } from './services/select-options.service';
import { UserService } from './services/user.service';
// provideClientHydration() // provideClientHydration()
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@ -16,9 +16,11 @@ export const appConfig: ApplicationConfig = {
{ provide: KeycloakService }, { provide: KeycloakService },
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
useFactory: initializeKeycloak, // useFactory: initializeKeycloak,
useFactory: (keycloakInitializer: KeycloakInitializerService) => async () => await keycloakInitializer.initialize(),
multi: true, multi: true,
deps: [KeycloakService], // deps: [KeycloakService],
deps: [KeycloakInitializerService],
}, },
{ {
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
@ -40,24 +42,17 @@ export const appConfig: ApplicationConfig = {
}), }),
), ),
provideAnimations(), provideAnimations(),
// {provide: LOCALE_ID, useValue: 'en-US' }
], ],
}; };
function initUserService(userService: UserService) {
return () => {
//selectOptions.init();
};
}
function initServices(selectOptions: SelectOptionsService) { function initServices(selectOptions: SelectOptionsService) {
return () => { return async () => {
selectOptions.init(); await selectOptions.init();
}; };
} }
function initializeKeycloak(keycloak: KeycloakService) { function initializeKeycloak(keycloak: KeycloakService) {
return () => return async () => {
keycloak.init({ const authenticated = await keycloak.init({
config: { config: {
url: environment.keycloak.url, url: environment.keycloak.url,
realm: environment.keycloak.realm, realm: environment.keycloak.realm,
@ -68,4 +63,6 @@ function initializeKeycloak(keycloak: KeycloakService) {
silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html', silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html',
}, },
}); });
console.log(`--->${authenticated}`);
};
} }

View File

@ -17,9 +17,9 @@
</div> </div>
<div class="col-12 md:col-3 text-500"> <div class="col-12 md:col-3 text-500">
<div class="text-black font-bold line-height-3 mb-3">Actions</div> <div class="text-black font-bold line-height-3 mb-3">Actions</div>
<a *ngIf="!userService.isLoggedIn()" (click)="login()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Login</a> <a *ngIf="!keycloakService.isLoggedIn()" (click)="login()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Login</a>
<a *ngIf="userService.isLoggedIn()" [routerLink]="['/account']" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Account</a> <a *ngIf="keycloakService.isLoggedIn()" [routerLink]="['/account']" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Account</a>
<a *ngIf="userService.isLoggedIn()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline" (click)="userService.logout()">Log Out</a> <a *ngIf="keycloakService.isLoggedIn()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline" (click)="keycloakService.logout()">Log Out</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { SidebarModule } from 'primeng/sidebar'; import { SidebarModule } from 'primeng/sidebar';
import { UserService } from '../../services/user.service'; import { KeycloakService } from '../../services/keycloak.service';
import { SharedModule } from '../../shared/shared/shared.module'; import { SharedModule } from '../../shared/shared/shared.module';
@Component({ @Component({
selector: 'footer', selector: 'footer',
@ -12,8 +12,10 @@ import { SharedModule } from '../../shared/shared/shared.module';
export class FooterComponent { export class FooterComponent {
privacyVisible = false; privacyVisible = false;
termsVisible = false; termsVisible = false;
constructor(public userService: UserService) {} constructor(public keycloakService: KeycloakService) {}
login() { login() {
this.userService.login(window.location.href); this.keycloakService.login({
redirectUri: window.location.href,
});
} }
} }

View File

@ -4,7 +4,9 @@
<p-tabMenu [model]="tabItems" ariaLabelledBy="label" styleClass="flex" [activeItem]="activeItem"> </p-tabMenu> <p-tabMenu [model]="tabItems" ariaLabelledBy="label" styleClass="flex" [activeItem]="activeItem"> </p-tabMenu>
<p-menubar [model]="menuItems"></p-menubar> <p-menubar [model]="menuItems"></p-menubar>
<p-menubar [model]="loginItems"></p-menubar> <p-menubar [model]="loginItems"></p-menubar>
<div *ngIf="user$ | async as user; else empty">Welcome, {{ user.firstName }}</div> @if(user){
<div>Welcome, {{ user.firstName }}</div>
}
<ng-template #empty> </ng-template> <ng-template #empty> </ng-template>
</div> </div>
</div> </div>

View File

@ -10,7 +10,8 @@ import { TabMenuModule } from 'primeng/tabmenu';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model'; import { KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { UserService } from '../../services/user.service'; import { KeycloakService } from '../../services/keycloak.service';
import { map2User } from '../../utils/utils';
@Component({ @Component({
selector: 'header', selector: 'header',
standalone: true, standalone: true,
@ -27,12 +28,14 @@ export class HeaderComponent {
public menuItems: MenuItem[]; public menuItems: MenuItem[];
activeItem; activeItem;
faUserGear = faUserGear; faUserGear = faUserGear;
constructor(public userService: UserService, private router: Router) {} constructor(public keycloakService: KeycloakService, private router: Router) {}
ngOnInit() { async ngOnInit() {
this.user$ = this.userService.getUserObservable(); const token = await this.keycloakService.getToken();
this.user$.subscribe(u => { this.user = map2User(token);
this.user = u; //this.user$ = this.keycloakService
// this.user$.subscribe(u => {
// this.user = u;
this.menuItems = [ this.menuItems = [
{ {
label: 'User Actions', label: 'User Actions',
@ -42,48 +45,48 @@ export class HeaderComponent {
label: 'Account', label: 'Account',
icon: 'pi pi-user', icon: 'pi pi-user',
routerLink: `/account`, routerLink: `/account`,
visible: this.isUserLogedIn(), visible: this.keycloakService.isLoggedIn(),
}, },
{ {
label: 'Create Listing', label: 'Create Listing',
icon: 'pi pi-plus-circle', icon: 'pi pi-plus-circle',
routerLink: '/createBusinessListing', routerLink: '/createBusinessListing',
visible: this.isUserLogedIn(), visible: this.keycloakService.isLoggedIn(),
}, },
{ {
label: 'My Listings', label: 'My Listings',
icon: 'pi pi-list', icon: 'pi pi-list',
routerLink: '/myListings', routerLink: '/myListings',
visible: this.isUserLogedIn(), visible: this.keycloakService.isLoggedIn(),
}, },
{ {
label: 'My Favorites', label: 'My Favorites',
icon: 'pi pi-star', icon: 'pi pi-star',
routerLink: '/myFavorites', routerLink: '/myFavorites',
visible: this.isUserLogedIn(), visible: this.keycloakService.isLoggedIn(),
}, },
{ {
label: 'EMail Us', label: 'EMail Us',
icon: 'fa-regular fa-envelope', icon: 'fa-regular fa-envelope',
routerLink: '/emailUs', routerLink: '/emailUs',
visible: this.isUserLogedIn(), visible: this.keycloakService.isLoggedIn(),
}, },
{ {
label: 'Logout', label: 'Logout',
icon: 'fa-solid fa-right-from-bracket', icon: 'fa-solid fa-right-from-bracket',
routerLink: '/logout', routerLink: '/logout',
visible: this.isUserLogedIn(), visible: this.keycloakService.isLoggedIn(),
}, },
{ {
label: 'Login', label: 'Login',
icon: 'fa-solid fa-right-from-bracket', icon: 'fa-solid fa-right-from-bracket',
command: () => this.login(), command: () => this.login(),
visible: !this.isUserLogedIn(), visible: !this.keycloakService.isLoggedIn(),
}, },
], ],
}, },
]; ];
}); // });
this.tabItems = [ this.tabItems = [
{ {
label: 'Businesses for Sale', label: 'Businesses for Sale',
@ -102,12 +105,12 @@ export class HeaderComponent {
{ {
label: 'Login', label: 'Login',
command: () => this.login(), command: () => this.login(),
visible: !this.isUserLogedIn(), visible: !this.keycloakService.isLoggedIn(),
}, },
{ {
label: 'Register', label: 'Register',
command: () => this.register(), command: () => this.register(),
visible: !this.isUserLogedIn(), visible: !this.keycloakService.isLoggedIn(),
}, },
]; ];
this.activeItem = this.tabItems[0]; this.activeItem = this.tabItems[0];
@ -116,13 +119,12 @@ export class HeaderComponent {
navigateWithState(dest: string, state: any) { navigateWithState(dest: string, state: any) {
this.router.navigate([dest], { state: state }); this.router.navigate([dest], { state: state });
} }
isUserLogedIn() {
return this.userService?.isLoggedIn();
}
login() { login() {
this.userService.login(window.location.href); this.keycloakService.login({
redirectUri: window.location.href,
});
} }
register() { register() {
this.userService.register(`${window.location.origin}/account`); this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
} }
} }

View File

@ -1,16 +1,17 @@
import { Component } from '@angular/core';
import { UserService } from '../../services/user.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { KeycloakService } from '../../services/keycloak.service';
@Component({ @Component({
selector: 'logout', selector: 'logout',
standalone: true, standalone: true,
imports: [CommonModule, RouterModule], imports: [CommonModule, RouterModule],
template:`` template: ``,
}) })
export class LogoutComponent { export class LogoutComponent {
constructor(private userService:UserService){ constructor(public keycloakService: KeycloakService) {
userService.logout(); sessionStorage.removeItem('USERID');
keycloakService.logout(window.location.origin + '/home');
} }
} }

View File

@ -1,22 +1,30 @@
import { CanMatchFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { CanMatchFn, Router, UrlTree } from '@angular/router';
// Services // 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<boolean | UrlTree> => { export const authGuard: CanMatchFn = async (route, segments): Promise<boolean | UrlTree> => {
const router = inject(Router); const router = inject(Router);
const userService = inject(UserService); const keycloakService = inject(KeycloakService);
const keycloakInitializer = inject(KeycloakInitializerService);
const authenticated: boolean = userService.isLoggedIn(); if (!keycloakInitializer.isInitialized()) {
await keycloakInitializer.initialize();
}
logger.info('###-> calling isLoggedIn()');
const authenticated = keycloakService.isLoggedIn();
if (!authenticated) { if (!authenticated) {
console.log(window.location.origin) console.log(window.location.origin);
console.log(window.location.href) console.log(window.location.href);
await userService.login(`${window.location.origin}${segments['url']}`); keycloakService.login({
redirectUri: `${window.location.origin}${segments['url']}`,
});
} }
// Get the user Keycloak roles and the required from the route // 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']; const requiredRoles = route.data?.['roles'];
// Allow the user to proceed if no additional roles are required to access the route // 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<boolean |
return true; return true;
} }
// Allow the user to proceed if ALL of the required roles are present const authorized = requiredRoles.every(role => roles.includes(role));
const authorized = requiredRoles.every((role) => roles.includes(role));
// Allow the user to proceed if ONE of the required roles is present
//const authorized = requiredRoles.some((role) => roles.includes(role));
if (authorized) { if (authorized) {
return true; return true;
} }
// Display my custom HTTP 403 access denied page return router.createUrlTree(['/home']);
return router.createUrlTree(['/access']);
}; };

View File

@ -1,5 +1,5 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable, inject } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, tap } from 'rxjs'; import { Observable, tap } from 'rxjs';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { LoadingService } from '../services/loading.service'; import { LoadingService } from '../services/loading.service';
@ -8,17 +8,15 @@ import { LoadingService } from '../services/loading.service';
export class LoadingInterceptor implements HttpInterceptor { export class LoadingInterceptor implements HttpInterceptor {
constructor(private loadingService: LoadingService) {} constructor(private loadingService: LoadingService) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
console.log("Intercepting Requests")
const requestId = `HTTP-${v4()}`; const requestId = `HTTP-${v4()}`;
this.loadingService.startLoading(requestId, request.url); this.loadingService.startLoading(requestId, request.url);
// return next.handle(request);
return next.handle(request).pipe( return next.handle(request).pipe(
tap({ tap({
finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist 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, // 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. // egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde.
}) }),
); );
} }
} }

View File

@ -1,3 +1,15 @@
/**
* @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 { export enum KeycloakEventType {
/** /**
* Called if there was an error during authentication. * Called if there was an error during authentication.
@ -33,7 +45,7 @@ export enum KeycloakEventType {
/** /**
* Called when a AIA has been requested by the application. * Called when a AIA has been requested by the application.
*/ */
OnActionUpdate OnActionUpdate,
} }
/** /**

View File

@ -11,14 +11,7 @@ import { HttpRequest } from '@angular/common/http';
/** /**
* HTTP Methods * HTTP Methods
*/ */
export type HttpMethods = export type HttpMethods = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH';
| 'GET'
| 'POST'
| 'PUT'
| 'DELETE'
| 'OPTIONS'
| 'HEAD'
| 'PATCH';
/** /**
* ExcludedUrl type may be used to specify the url and the HTTP method that * ExcludedUrl type may be used to specify the url and the HTTP method that

View File

@ -9,6 +9,7 @@ import { BusinessListing, User } from '../../../../../../bizmatch-server/src/mod
import { KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model'; import { KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { HistoryService } from '../../../services/history.service'; import { HistoryService } from '../../../services/history.service';
import { KeycloakService } from '../../../services/keycloak.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { MailService } from '../../../services/mail.service'; import { MailService } from '../../../services/mail.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
@ -64,6 +65,7 @@ export class DetailsBusinessListingComponent {
private messageService: MessageService, private messageService: MessageService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
public historyService: HistoryService, public historyService: HistoryService,
public keycloakService: KeycloakService,
) { ) {
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
@ -71,9 +73,7 @@ export class DetailsBusinessListingComponent {
} }
}); });
this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl }; this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl };
this.userService.getUserObservable().subscribe(user => { this.user;
this.user = user;
});
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler); this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
} }
@ -84,7 +84,7 @@ export class DetailsBusinessListingComponent {
} }
isAdmin() { isAdmin() {
return this.userService.hasAdminRole(); return this.keycloakService.getUserRoles(true).includes('ADMIN');
} }
async mail() { async mail() {
this.mailinfo.email = this.listingUser.email; this.mailinfo.email = this.listingUser.email;

View File

@ -9,12 +9,13 @@ import { CommercialPropertyListing, User } from '../../../../../../bizmatch-serv
import { ErrorResponse, KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model'; import { ErrorResponse, KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { HistoryService } from '../../../services/history.service'; import { HistoryService } from '../../../services/history.service';
import { KeycloakService } from '../../../services/keycloak.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { MailService } from '../../../services/mail.service'; import { MailService } from '../../../services/mail.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils'; import { getCriteriaStateObject, getSessionStorageHandler, map2User } from '../../../utils/utils';
@Component({ @Component({
selector: 'app-details-commercial-property-listing', selector: 'app-details-commercial-property-listing',
@ -65,22 +66,23 @@ export class DetailsCommercialPropertyListingComponent {
private messageService: MessageService, private messageService: MessageService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
public historyService: HistoryService, public historyService: HistoryService,
public keycloakService: KeycloakService,
) { ) {
this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl }; this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl };
this.userService.getUserObservable().subscribe(user => {
this.user = user;
});
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler); this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
} }
async ngOnInit() { async ngOnInit() {
const token = await this.keycloakService.getToken();
this.user = map2User(token);
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty')); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id); this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
this.listingUser = await this.userService.getById(this.listing.userId); this.listingUser = await this.userService.getById(this.listing.userId);
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description); this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
} }
isAdmin() { isAdmin() {
return this.userService.hasAdminRole(); return this.keycloakService.getUserRoles(true).includes('ADMIN');
} }
async mail() { async mail() {
this.mailinfo.email = this.listingUser.email; this.mailinfo.email = this.listingUser.email;

View File

@ -138,7 +138,7 @@
</ul> </ul>
</div> </div>
</div> </div>
@if( user?.email===(user$| async)?.email || isAdmin()){ @if( user?.email===keycloakUser?.email || isAdmin()){
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/account', user.id]"></button> <button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/account', user.id]"></button>
} }
</div> </div>

View File

@ -9,10 +9,12 @@ import { KeycloakUser, ListingCriteria } from '../../../../../../bizmatch-server
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { HistoryService } from '../../../services/history.service'; import { HistoryService } from '../../../services/history.service';
import { ImageService } from '../../../services/image.service'; import { ImageService } from '../../../services/image.service';
import { KeycloakService } from '../../../services/keycloak.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { map2User } from '../../../utils/utils';
@Component({ @Component({
selector: 'app-details-user', selector: 'app-details-user',
@ -26,6 +28,7 @@ export class DetailsUserComponent {
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined; private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
user: User; user: User;
user$: Observable<KeycloakUser>; user$: Observable<KeycloakUser>;
keycloakUser: KeycloakUser;
environment = environment; environment = environment;
criteria: ListingCriteria; criteria: ListingCriteria;
businessListings: BusinessListing[]; businessListings: BusinessListing[];
@ -44,6 +47,7 @@ export class DetailsUserComponent {
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private imageService: ImageService, private imageService: ImageService,
public historyService: HistoryService, public historyService: HistoryService,
public keycloakService: KeycloakService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -53,12 +57,14 @@ export class DetailsUserComponent {
// Zuweisen der Ergebnisse zu den Member-Variablen der Klasse // Zuweisen der Ergebnisse zu den Member-Variablen der Klasse
this.businessListings = results[0]; this.businessListings = results[0];
this.commercialPropListings = results[1]; 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.companyOverview = this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview);
this.offeredServices = this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices); this.offeredServices = this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices);
} }
isAdmin() { isAdmin() {
return this.userService.hasAdminRole(); return this.keycloakService.getUserRoles(true).includes('ADMIN');
} }
} }

View File

@ -5,7 +5,7 @@
<div class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full left-0 top-100 px-6 lg:px-0 shadow-2 lg:shadow-none z-2"> <div class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full left-0 top-100 px-6 lg:px-0 shadow-2 lg:shadow-none z-2">
<section></section> <section></section>
<div class="flex justify-content-between lg:block border-top-1 lg:border-top-none border-gray-800 py-3 lg:py-0 mt-3 lg:mt-0"> <div class="flex justify-content-between lg:block border-top-1 lg:border-top-none border-gray-800 py-3 lg:py-0 mt-3 lg:mt-0">
@if(userService.isLoggedIn()){ @if(keycloakService.isLoggedIn()){
<p-button label="Account" class="ml-3 font-bold" [outlined]="true" severity="secondary" [routerLink]="['/account']"></p-button> <p-button label="Account" class="ml-3 font-bold" [outlined]="true" severity="secondary" [routerLink]="['/account']"></p-button>
} @else { } @else {
<p-button label="Log In" class="ml-3 font-bold" [outlined]="true" severity="secondary" (click)="login()"></p-button> <p-button label="Log In" class="ml-3 font-bold" [outlined]="true" severity="secondary" (click)="login()"></p-button>
@ -94,7 +94,7 @@
pRipple pRipple
label="Create Your Listing" label="Create Your Listing"
class="block mt-7 mb-7 lg:mb-0 p-button-rounded p-button-success p-button-lg font-medium" class="block mt-7 mb-7 lg:mb-0 p-button-rounded p-button-success p-button-lg font-medium"
[routerLink]="userService.isLoggedIn() ? '/createBusinessListing' : '/pricing'" [routerLink]="keycloakService.isLoggedIn() ? '/createBusinessListing' : '/pricing'"
></button> ></button>
</div> </div>
</div> </div>

View File

@ -8,11 +8,10 @@ import { CheckboxModule } from 'primeng/checkbox';
import { DropdownModule } from 'primeng/dropdown'; import { DropdownModule } from 'primeng/dropdown';
import { InputTextModule } from 'primeng/inputtext'; import { InputTextModule } from 'primeng/inputtext';
import { StyleClassModule } from 'primeng/styleclass'; import { StyleClassModule } from 'primeng/styleclass';
import { Observable } from 'rxjs'; import { ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
import { KeycloakUser, ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model'; import { KeycloakService } from '../../services/keycloak.service';
import { ListingsService } from '../../services/listings.service'; import { ListingsService } from '../../services/listings.service';
import { SelectOptionsService } from '../../services/select-options.service'; import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service';
import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../utils/utils'; import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../utils/utils';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
@ -27,14 +26,12 @@ export class HomeComponent {
maxPrice: string; maxPrice: string;
minPrice: string; minPrice: string;
criteria: ListingCriteria; criteria: ListingCriteria;
user$: Observable<KeycloakUser>;
states = []; 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); this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
resetCriteria(this.criteria); resetCriteria(this.criteria);
} }
async ngOnInit() { async ngOnInit() {
this.user$ = this.userService.getUserObservable();
if (this.activeTabAction === 'business' || this.activeTabAction === 'commercialProperty') { if (this.activeTabAction === 'business' || this.activeTabAction === 'commercialProperty') {
const statesResult = await this.listingsService.getAllStates(this.activeTabAction); 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 })); 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() { login() {
this.userService.login(window.location.href); this.keycloakService.login({
redirectUri: window.location.href,
});
} }
register() { register() {
this.userService.register(`${window.location.origin}/account`); this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
} }
} }

View File

@ -47,12 +47,7 @@
</a> </a>
</li> </li>
<li> <li>
<a <a (click)="logout()" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
(click)="userService.logout()"
routerLinkActive="text-blue-500"
pRipple
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
>
<fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon> <fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon>
<span class="font-medium hidden md:block">Logout</span> <span class="font-medium hidden md:block">Logout</span>
</a> </a>

View File

@ -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 { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton'; import { Component } from '@angular/core';
import { TagModule } from 'primeng/tag'; import { NavigationEnd, Router, RouterModule } from '@angular/router';
import data from '../../../assets/data/listings.json'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
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 { faEnvelope } from '@fortawesome/free-regular-svg-icons'; import { faEnvelope } from '@fortawesome/free-regular-svg-icons';
import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons'; import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { ButtonModule } from 'primeng/button';
import { UserService } from '../../services/user.service'; import { DividerModule } from 'primeng/divider';
import { RippleModule } from 'primeng/ripple';
import { StyleClassModule } from 'primeng/styleclass';
import { KeycloakService } from '../../services/keycloak.service';
@Component({ @Component({
selector: 'menu-account', selector: 'menu-account',
standalone: true, standalone: true,
imports: [CommonModule, StyleClassModule, ButtonModule, DividerModule, RouterModule, RippleModule, FontAwesomeModule], imports: [CommonModule, StyleClassModule, ButtonModule, DividerModule, RouterModule, RippleModule, FontAwesomeModule],
templateUrl: './menu-account.component.html', templateUrl: './menu-account.component.html',
styleUrl: './menu-account.component.scss' styleUrl: './menu-account.component.scss',
}) })
export class MenuAccountComponent { export class MenuAccountComponent {
activeLink: string; activeLink: string;
faEnvelope = faEnvelope; faEnvelope = faEnvelope;
faRightFromBracket = faRightFromBracket; faRightFromBracket = faRightFromBracket;
constructor(private router: Router,public userService:UserService) { constructor(private router: Router, public keycloakService: KeycloakService) {
// Abonniere Router-Events, um den aktiven Link zu ermitteln // Abonniere Router-Events, um den aktiven Link zu ermitteln
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
@ -39,4 +29,8 @@ export class MenuAccountComponent {
} }
}); });
} }
logout() {
sessionStorage.removeItem('USERID');
this.keycloakService.logout(window.location.origin + '/home');
}
} }

View File

@ -1,17 +1,17 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { KeycloakService } from '../../services/keycloak.service';
import { SharedModule } from '../../shared/shared/shared.module'; import { SharedModule } from '../../shared/shared/shared.module';
import { UserService } from '../../services/user.service';
@Component({ @Component({
selector: 'app-pricing', selector: 'app-pricing',
standalone: true, standalone: true,
imports: [SharedModule], imports: [SharedModule],
templateUrl: './pricing.component.html', templateUrl: './pricing.component.html',
styleUrl: './pricing.component.scss' styleUrl: './pricing.component.scss',
}) })
export class PricingComponent { export class PricingComponent {
constructor(private userService:UserService){} constructor(public keycloakService: KeycloakService) {}
register() { register() {
this.userService.register(`${window.location.origin}/account`); this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
} }
} }

View File

@ -17,11 +17,13 @@ import { environment } from '../../../../environments/environment';
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component'; import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
import { GeoService } from '../../../services/geo.service'; import { GeoService } from '../../../services/geo.service';
import { ImageService } from '../../../services/image.service'; import { ImageService } from '../../../services/image.service';
import { KeycloakService } from '../../../services/keycloak.service';
import { LoadingService } from '../../../services/loading.service'; import { LoadingService } from '../../../services/loading.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { SubscriptionsService } from '../../../services/subscriptions.service'; import { SubscriptionsService } from '../../../services/subscriptions.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { map2User } from '../../../utils/utils';
import { TOOLBAR_OPTIONS } from '../../utils/defaults'; import { TOOLBAR_OPTIONS } from '../../utils/defaults';
@Component({ @Component({
selector: 'app-account', selector: 'app-account',
@ -60,12 +62,14 @@ export class AccountComponent {
public dialogService: DialogService, public dialogService: DialogService,
private confirmationService: ConfirmationService, private confirmationService: ConfirmationService,
private imageService: ImageService, private imageService: ImageService,
private keycloakService: KeycloakService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
if (this.id) { if (this.id) {
this.user = await this.userService.getById(this.id); this.user = await this.userService.getById(this.id);
} else { } else {
const keycloakUser = this.userService.getKeycloakUser(); const token = await this.keycloakService.getToken();
const keycloakUser = map2User(token);
const email = keycloakUser.email; const email = keycloakUser.email;
try { try {
this.user = await this.userService.getByMail(email); this.user = await this.userService.getByMail(email);

View File

@ -3,7 +3,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.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 { DragDropModule } from '@angular/cdk/drag-drop';
import { faTrash } from '@fortawesome/free-solid-svg-icons'; 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 { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
import { GeoService } from '../../../services/geo.service'; import { GeoService } from '../../../services/geo.service';
import { ImageService } from '../../../services/image.service'; import { ImageService } from '../../../services/image.service';
import { KeycloakService } from '../../../services/keycloak.service';
import { LoadingService } from '../../../services/loading.service'; import { LoadingService } from '../../../services/loading.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
@ -99,6 +100,7 @@ export class EditBusinessListingComponent {
public dialogService: DialogService, public dialogService: DialogService,
private confirmationService: ConfirmationService, private confirmationService: ConfirmationService,
private route: ActivatedRoute, private route: ActivatedRoute,
private keycloakService: KeycloakService,
) { ) {
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) { if (event instanceof NavigationEnd) {
@ -115,11 +117,13 @@ export class EditBusinessListingComponent {
}); });
} }
async ngOnInit() { async ngOnInit() {
const token = await this.keycloakService.getToken();
const keycloakUser = map2User(token);
if (this.mode === 'edit') { if (this.mode === 'edit') {
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business')); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
} else { } else {
this.listing = createDefaultBusinessListing(); this.listing = createDefaultBusinessListing();
this.listing.userId = await this.userService.getId(); this.listing.userId = await this.userService.getId(keycloakUser.email);
if (this.data) { if (this.data) {
this.listing.title = this.data?.title; this.listing.title = this.data?.title;
this.listing.description = this.data?.description; this.listing.description = this.data?.description;

View File

@ -3,7 +3,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.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 { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
import { HttpEventType } from '@angular/common/http'; 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 { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
import { GeoService } from '../../../services/geo.service'; import { GeoService } from '../../../services/geo.service';
import { ImageService } from '../../../services/image.service'; import { ImageService } from '../../../services/image.service';
import { KeycloakService } from '../../../services/keycloak.service';
import { LoadingService } from '../../../services/loading.service'; import { LoadingService } from '../../../services/loading.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
@ -106,6 +107,7 @@ export class EditCommercialPropertyListingComponent {
public dialogService: DialogService, public dialogService: DialogService,
private confirmationService: ConfirmationService, private confirmationService: ConfirmationService,
private route: ActivatedRoute, private route: ActivatedRoute,
private keycloakService: KeycloakService,
) { ) {
// Abonniere Router-Events, um den aktiven Link zu ermitteln // Abonniere Router-Events, um den aktiven Link zu ermitteln
this.router.events.subscribe(event => { this.router.events.subscribe(event => {
@ -123,11 +125,13 @@ export class EditCommercialPropertyListingComponent {
}); });
} }
async ngOnInit() { async ngOnInit() {
const token = await this.keycloakService.getToken();
const keycloakUser = map2User(token);
if (this.mode === 'edit') { if (this.mode === 'edit') {
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty')); this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
} else { } else {
this.listing = createDefaultCommercialPropertyListing(); this.listing = createDefaultCommercialPropertyListing();
this.listing.userId = await this.userService.getId(); this.listing.userId = await this.userService.getId(keycloakUser.email);
this.listing.imagePath = uuidv4(); this.listing.imagePath = uuidv4();
if (this.data) { if (this.data) {
this.listing.title = this.data?.title; this.listing.title = this.data?.title;

View File

@ -1,9 +1,10 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { KeycloakUser, ListingType } from '../../../../../../bizmatch-server/src/models/main.model'; import { KeycloakUser, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
import { KeycloakService } from '../../../services/keycloak.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { map2User } from '../../../utils/utils';
import { MenuAccountComponent } from '../../menu-account/menu-account.component'; import { MenuAccountComponent } from '../../menu-account/menu-account.component';
@Component({ @Component({
@ -17,11 +18,10 @@ export class FavoritesComponent {
user: KeycloakUser; user: KeycloakUser;
listings: Array<ListingType> = []; //= dataListings as unknown as Array<BusinessListing>; listings: Array<ListingType> = []; //= dataListings as unknown as Array<BusinessListing>;
favorites: Array<ListingType>; favorites: Array<ListingType>;
constructor(public userService: UserService, private listingsService: ListingsService, public selectOptions: SelectOptionsService) { constructor(public keycloakService: KeycloakService, private listingsService: ListingsService, public selectOptions: SelectOptionsService) {}
this.user = this.userService.getKeycloakUser();
}
async ngOnInit() { 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)); this.favorites = this.listings.filter(l => l.favoritesForUser?.includes(this.user.id));
} }
} }

View File

@ -2,10 +2,12 @@ import { ChangeDetectorRef, Component } from '@angular/core';
import { ConfirmationService, MessageService } from 'primeng/api'; import { ConfirmationService, MessageService } from 'primeng/api';
import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model'; import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model'; import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
import { KeycloakService } from '../../../services/keycloak.service';
import { ListingsService } from '../../../services/listings.service'; import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service'; import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service'; import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module'; import { SharedModule } from '../../../shared/shared/shared.module';
import { map2User } from '../../../utils/utils';
import { MenuAccountComponent } from '../../menu-account/menu-account.component'; import { MenuAccountComponent } from '../../menu-account/menu-account.component';
@Component({ @Component({
selector: 'app-my-listing', selector: 'app-my-listing',
@ -21,6 +23,7 @@ export class MyListingComponent {
user: User; user: User;
constructor( constructor(
public userService: UserService, public userService: UserService,
public keycloakService: KeycloakService,
private listingsService: ListingsService, private listingsService: ListingsService,
private cdRef: ChangeDetectorRef, private cdRef: ChangeDetectorRef,
public selectOptions: SelectOptionsService, public selectOptions: SelectOptionsService,
@ -28,7 +31,9 @@ export class MyListingComponent {
private messageService: MessageService, private messageService: MessageService,
) {} ) {}
async ngOnInit() { 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; const email = keycloakUser.email;
this.user = await this.userService.getByMail(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')]); const result = await Promise.all([await this.listingsService.getListingByUserId(this.user.id, 'business'), await this.listingsService.getListingByUserId(this.user.id, 'commercialProperty')]);

View File

@ -0,0 +1,15 @@
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { KeycloakService } from '../services/keycloak.service';
export const authResolver: ResolveFn<boolean> = async (route, state) => {
const keycloakService: KeycloakService = inject(KeycloakService);
if (!keycloakService.isLoggedIn()) {
await keycloakService.login({
redirectUri: window.location.href,
});
}
return true;
};

View File

@ -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<void> {
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: (<any>window).location.origin + '/assets/silent-check-sso.html',
},
});
logger.info(`--->${authenticated}`);
this.initialized = true;
}
isInitialized(): boolean {
return this.initialized;
}
}

View File

@ -6,16 +6,14 @@
* found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md * 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 { HttpHeaders, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import Keycloak from 'keycloak-js';
import { Subject, from } from 'rxjs'; import { Subject, from } from 'rxjs';
import { map } from 'rxjs/operators'; 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 { 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 * Service to expose existent methods from the Keycloak JS adapter, adding new
@ -64,8 +62,7 @@ export class KeycloakService {
/** /**
* Observer for the keycloak events * Observer for the keycloak events
*/ */
private _keycloakEvents$: Subject<KeycloakEvent> = private _keycloakEvents$: Subject<KeycloakEvent> = new Subject<KeycloakEvent>();
new Subject<KeycloakEvent>();
/** /**
* The amount of required time remaining before expiry of the token before the token will be refreshed. * 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. * argument if the source function provides any.
*/ */
private bindsKeycloakEvents(): void { private bindsKeycloakEvents(): void {
this._instance.onAuthError = (errorData) => { this._instance.onAuthError = errorData => {
this._keycloakEvents$.next({ this._keycloakEvents$.next({
args: errorData, args: errorData,
type: KeycloakEventType.OnAuthError type: KeycloakEventType.OnAuthError,
}); });
}; };
@ -100,13 +97,13 @@ export class KeycloakService {
this._instance.onAuthRefreshSuccess = () => { this._instance.onAuthRefreshSuccess = () => {
this._keycloakEvents$.next({ this._keycloakEvents$.next({
type: KeycloakEventType.OnAuthRefreshSuccess type: KeycloakEventType.OnAuthRefreshSuccess,
}); });
}; };
this._instance.onAuthRefreshError = () => { this._instance.onAuthRefreshError = () => {
this._keycloakEvents$.next({ this._keycloakEvents$.next({
type: KeycloakEventType.OnAuthRefreshError type: KeycloakEventType.OnAuthRefreshError,
}); });
}; };
@ -116,21 +113,21 @@ export class KeycloakService {
this._instance.onTokenExpired = () => { this._instance.onTokenExpired = () => {
this._keycloakEvents$.next({ this._keycloakEvents$.next({
type: KeycloakEventType.OnTokenExpired type: KeycloakEventType.OnTokenExpired,
}); });
}; };
this._instance.onActionUpdate = (state) => { this._instance.onActionUpdate = state => {
this._keycloakEvents$.next({ this._keycloakEvents$.next({
args: state, args: state,
type: KeycloakEventType.OnActionUpdate type: KeycloakEventType.OnActionUpdate,
}); });
}; };
this._instance.onReady = (authenticated) => { this._instance.onReady = authenticated => {
this._keycloakEvents$.next({ this._keycloakEvents$.next({
args: authenticated, args: authenticated,
type: KeycloakEventType.OnReady type: KeycloakEventType.OnReady,
}); });
}; };
} }
@ -142,9 +139,7 @@ export class KeycloakService {
* @param bearerExcludedUrls array of strings or ExcludedUrl that includes * @param bearerExcludedUrls array of strings or ExcludedUrl that includes
* the url and HttpMethod. * the url and HttpMethod.
*/ */
private loadExcludedUrls( private loadExcludedUrls(bearerExcludedUrls: (string | ExcludedUrl)[]): ExcludedUrlRegex[] {
bearerExcludedUrls: (string | ExcludedUrl)[]
): ExcludedUrlRegex[] {
const excludedUrls: ExcludedUrlRegex[] = []; const excludedUrls: ExcludedUrlRegex[] = [];
for (const item of bearerExcludedUrls) { for (const item of bearerExcludedUrls) {
let excludedUrl: ExcludedUrlRegex; let excludedUrl: ExcludedUrlRegex;
@ -153,7 +148,7 @@ export class KeycloakService {
} else { } else {
excludedUrl = { excludedUrl = {
urlPattern: new RegExp(item.url, 'i'), urlPattern: new RegExp(item.url, 'i'),
httpMethods: item.httpMethods httpMethods: item.httpMethods,
}; };
} }
excludedUrls.push(excludedUrl); excludedUrls.push(excludedUrl);
@ -175,7 +170,7 @@ export class KeycloakService {
initOptions, initOptions,
updateMinValidity = 20, updateMinValidity = 20,
shouldAddToken = () => true, shouldAddToken = () => true,
shouldUpdateToken = () => true shouldUpdateToken = () => true,
}: KeycloakOptions): void { }: KeycloakOptions): void {
this._enableBearerInterceptor = enableBearerInterceptor; this._enableBearerInterceptor = enableBearerInterceptor;
this._loadUserProfileAtStartUp = loadUserProfileAtStartUp; this._loadUserProfileAtStartUp = loadUserProfileAtStartUp;
@ -285,7 +280,7 @@ export class KeycloakService {
*/ */
public async logout(redirectUri?: string) { public async logout(redirectUri?: string) {
const options = { const options = {
redirectUri redirectUri,
}; };
await this._instance.logout(options); await this._instance.logout(options);
@ -302,9 +297,7 @@ export class KeycloakService {
* @returns * @returns
* A void Promise if the register flow was successful. * A void Promise if the register flow was successful.
*/ */
public async register( public async register(options: Keycloak.KeycloakLoginOptions = { action: 'register' }) {
options: Keycloak.KeycloakLoginOptions = { action: 'register' }
) {
await this._instance.register(options); await this._instance.register(options);
} }
@ -345,7 +338,7 @@ export class KeycloakService {
let roles: string[] = []; let roles: string[] = [];
if (this._instance.resourceAccess) { if (this._instance.resourceAccess) {
Object.keys(this._instance.resourceAccess).forEach((key) => { Object.keys(this._instance.resourceAccess).forEach(key => {
if (resource && resource !== key) { if (resource && resource !== key) {
return; return;
} }
@ -407,9 +400,7 @@ export class KeycloakService {
// is not implemented, avoiding the redirect loop. // is not implemented, avoiding the redirect loop.
if (this._silentRefresh) { if (this._silentRefresh) {
if (this.isTokenExpired()) { if (this.isTokenExpired()) {
throw new Error( throw new Error('Failed to refresh the token, or the session is expired');
'Failed to refresh the token, or the session is expired'
);
} }
return true; return true;
@ -442,16 +433,14 @@ export class KeycloakService {
} }
if (!this._instance.authenticated) { if (!this._instance.authenticated) {
throw new Error( throw new Error('The user profile was not loaded as the user is not logged in.');
'The user profile was not loaded as the user is not logged in.'
);
} }
return (this._userProfile = await this._instance.loadUserProfile()); 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() { public async getToken() {
return this._instance.token; return this._instance.token;
@ -491,16 +480,7 @@ export class KeycloakService {
* An observable with with the HTTP Authorization header and the current token. * An observable with with the HTTP Authorization header and the current token.
*/ */
public addTokenToHeader(headers: HttpHeaders = new HttpHeaders()) { public addTokenToHeader(headers: HttpHeaders = new HttpHeaders()) {
return from(this.getToken()).pipe( return from(this.getToken()).pipe(map(token => (token ? headers.set(this._authorizationHeaderName, this._bearerPrefix + token) : headers)));
map((token) =>
token
? headers.set(
this._authorizationHeaderName,
this._bearerPrefix + token
)
: headers
)
);
} }
/** /**

View File

@ -1,107 +1,18 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable, Signal, computed, effect, signal } from '@angular/core'; import { Injectable } from '@angular/core';
import { jwtDecode } from 'jwt-decode'; import { lastValueFrom } from 'rxjs';
import { Observable, distinctUntilChanged, filter, from, lastValueFrom, map } from 'rxjs';
import urlcat from 'urlcat'; import urlcat from 'urlcat';
import { User } from '../../../../bizmatch-server/src/models/db.model'; 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 { environment } from '../../environments/environment';
import { KeycloakService } from './keycloak.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class UserService { export class UserService {
private apiBaseUrl = environment.apiBaseUrl; private apiBaseUrl = environment.apiBaseUrl;
// -----------------------------
// Keycloak services
// -----------------------------
private user$ = new Observable<KeycloakUser>();
private user: KeycloakUser;
public $isLoggedIn: Signal<boolean>;
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 () => { constructor(private http: HttpClient) {}
if (this.$isLoggedIn()) {
this.updateTokenDetails();
} else {
this.user = null;
}
});
}
private async refreshToken(): Promise<void> {
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<void> {
const token = await this.keycloak.getToken();
this.user = this.map2User(token);
}
private map2User(jwt: string): KeycloakUser {
const token = jwtDecode<JwtToken>(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<KeycloakUser> {
return this.user$;
}
async getId(): Promise<string> {
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 });
}
// ----------------------------- // -----------------------------
// DB services // DB services
@ -122,4 +33,13 @@ export class UserService {
async getAllStates(): Promise<any> { async getAllStates(): Promise<any> {
return await lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/user/states/all`)); return await lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/user/states/all`));
} }
async getId(email: string): Promise<string> {
if (sessionStorage.getItem('USERID')) {
return sessionStorage.getItem('USERID');
} else {
const user = await this.getByMail(email);
sessionStorage.setItem('USERID', user.id);
return user.id;
}
}
} }

View File

@ -1,7 +1,8 @@
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan'; 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 { 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 { export function createDefaultCommercialPropertyListing(): CommercialPropertyListing {
return { return {
@ -116,3 +117,16 @@ export function resetCriteria(criteria: ListingCriteria) {
criteria.title = null; criteria.title = null;
criteria.name = null; criteria.name = null;
} }
export function map2User(jwt: string): KeycloakUser {
if (jwt) {
const token = jwtDecode<JwtToken>(jwt);
return {
id: token.user_id,
firstName: token.given_name,
lastName: token.family_name,
email: token.email,
};
} else {
return null;
}
}