new initialization process, keycloak update 24.0.4

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

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
*/
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$;
}
}
}

View File

@@ -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;
}
}
}