new initialization process, keycloak update 24.0.4
This commit is contained in:
37
bizmatch/src/app/services/keycloak-initializer.service.ts
Normal file
37
bizmatch/src/app/services/keycloak-initializer.service.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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<KeycloakEvent> =
|
||||
new Subject<KeycloakEvent>();
|
||||
private _keycloakEvents$: Subject<KeycloakEvent> = new Subject<KeycloakEvent>();
|
||||
/**
|
||||
* 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<KeycloakEvent> {
|
||||
return this._keycloakEvents$;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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 () => {
|
||||
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 });
|
||||
}
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
// -----------------------------
|
||||
// DB services
|
||||
@@ -122,4 +33,13 @@ export class UserService {
|
||||
async getAllStates(): Promise<any> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user