@codex-ts/core-lib 1.0.18 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/codex-ts-core-lib.mjs +87 -195
- package/fesm2022/codex-ts-core-lib.mjs.map +1 -1
- package/lib/keycloak/keycloak-config.interface.d.ts +15 -54
- package/lib/keycloak/keycloak.guard.d.ts +1 -0
- package/lib/keycloak/keycloak.interceptor.d.ts +2 -3
- package/lib/keycloak/keycloak.providers.d.ts +1 -2
- package/lib/keycloak/keycloak.service.d.ts +13 -53
- package/package.json +3 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, PLATFORM_ID, signal, computed, Injectable, importProvidersFrom, APP_INITIALIZER, Component, NgModule, Directive, Input, ViewChild, Pipe } from '@angular/core';
|
|
3
|
-
import * as i1 from '@angular/common/http';
|
|
4
|
-
import { HttpClient, HTTP_INTERCEPTORS, HttpClientModule, HttpParams } from '@angular/common/http';
|
|
5
3
|
import * as i1$2 from '@angular/common';
|
|
6
4
|
import { isPlatformBrowser, CommonModule, formatDate } from '@angular/common';
|
|
5
|
+
import Keycloak from 'keycloak-js';
|
|
6
|
+
import * as i1 from '@angular/common/http';
|
|
7
|
+
import { HTTP_INTERCEPTORS, HttpClientModule, HttpParams } from '@angular/common/http';
|
|
7
8
|
import { from, mergeMap, catchError, throwError, map, BehaviorSubject, finalize, Observable, forkJoin, of, isObservable, Subject, take as take$1, tap as tap$1, takeUntil as takeUntil$1, ReplaySubject, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
8
9
|
import * as i1$3 from '@angular/router';
|
|
9
10
|
import { Router } from '@angular/router';
|
|
@@ -138,30 +139,10 @@ var TaskRole;
|
|
|
138
139
|
|
|
139
140
|
const KEYCLOAK_CONFIG = new InjectionToken('KEYCLOAK_CONFIG');
|
|
140
141
|
|
|
141
|
-
/** Keycloak error types */
|
|
142
|
-
var KeycloakErrorType;
|
|
143
|
-
(function (KeycloakErrorType) {
|
|
144
|
-
KeycloakErrorType["SCRIPT_LOAD"] = "SCRIPT_LOAD_ERROR";
|
|
145
|
-
KeycloakErrorType["INIT"] = "INIT_ERROR";
|
|
146
|
-
KeycloakErrorType["TOKEN_REFRESH"] = "TOKEN_REFRESH_ERROR";
|
|
147
|
-
KeycloakErrorType["PROFILE_LOAD"] = "PROFILE_LOAD_ERROR";
|
|
148
|
-
})(KeycloakErrorType || (KeycloakErrorType = {}));
|
|
149
|
-
/** Keycloak error with type and details */
|
|
150
|
-
class KeycloakError extends Error {
|
|
151
|
-
type;
|
|
152
|
-
originalError;
|
|
153
|
-
constructor(type, message, originalError) {
|
|
154
|
-
super(message);
|
|
155
|
-
this.type = type;
|
|
156
|
-
this.originalError = originalError;
|
|
157
|
-
this.name = 'KeycloakError';
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
142
|
/**
|
|
161
143
|
* Service for handling Keycloak authentication and authorization
|
|
162
144
|
*/
|
|
163
145
|
class KeycloakService {
|
|
164
|
-
http = inject(HttpClient);
|
|
165
146
|
platformId = inject(PLATFORM_ID);
|
|
166
147
|
config = inject(KEYCLOAK_CONFIG);
|
|
167
148
|
isBrowser = isPlatformBrowser(this.platformId);
|
|
@@ -170,63 +151,26 @@ class KeycloakService {
|
|
|
170
151
|
#userProfile = signal(null);
|
|
171
152
|
isAuthenticated$ = computed(() => this.#isAuthenticated());
|
|
172
153
|
userProfile$ = computed(() => this.#userProfile());
|
|
154
|
+
token$ = computed(() => this.#keycloakInstance()?.token);
|
|
173
155
|
/** Check if URL should be excluded from bearer token */
|
|
174
156
|
isUrlExcluded(url) {
|
|
175
|
-
return this.config.
|
|
176
|
-
}
|
|
177
|
-
/** Validate Keycloak configuration */
|
|
178
|
-
validateConfig() {
|
|
179
|
-
if (!this.config.authServerUrl) {
|
|
180
|
-
throw new KeycloakError(KeycloakErrorType.INIT, 'Auth server URL is required');
|
|
181
|
-
}
|
|
182
|
-
if (!this.config.realm) {
|
|
183
|
-
throw new KeycloakError(KeycloakErrorType.INIT, 'Realm is required');
|
|
184
|
-
}
|
|
185
|
-
if (!this.config.clientId) {
|
|
186
|
-
throw new KeycloakError(KeycloakErrorType.INIT, 'Client ID is required');
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
loadKeycloakScript() {
|
|
190
|
-
if (!this.isBrowser)
|
|
191
|
-
return Promise.resolve();
|
|
192
|
-
if (typeof Keycloak !== 'undefined')
|
|
193
|
-
return Promise.resolve();
|
|
194
|
-
return new Promise((resolve, reject) => {
|
|
195
|
-
const script = document.createElement('script');
|
|
196
|
-
script.src = `${this.config.authServerUrl}/js/keycloak.js`;
|
|
197
|
-
script.async = true;
|
|
198
|
-
script.onload = () => resolve();
|
|
199
|
-
script.onerror = () => reject(new Error('Failed to load Keycloak script'));
|
|
200
|
-
document.head.appendChild(script);
|
|
201
|
-
});
|
|
157
|
+
return this.config.options?.excludedUrls?.some(excluded => url.startsWith(excluded) || new RegExp(excluded).test(url)) ?? false;
|
|
202
158
|
}
|
|
203
|
-
|
|
204
|
-
if (!this.isBrowser)
|
|
205
|
-
return '';
|
|
206
|
-
// Use the configured URL or fallback to a default location
|
|
207
|
-
return this.config.silentCheckSsoRedirectUri ??
|
|
208
|
-
`${window.location.origin}/assets/silent-check-sso.html`;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Initialize Keycloak authentication
|
|
212
|
-
* @returns Promise resolving to true if initialization is successful
|
|
213
|
-
* @throws KeycloakError if initialization fails
|
|
214
|
-
*/
|
|
159
|
+
/** Initialize Keycloak authentication */
|
|
215
160
|
async initialize() {
|
|
216
161
|
if (!this.isBrowser)
|
|
217
162
|
return false;
|
|
163
|
+
const keycloak = new Keycloak({
|
|
164
|
+
url: this.config.authServerUrl,
|
|
165
|
+
realm: this.config.realm,
|
|
166
|
+
clientId: this.config.clientId
|
|
167
|
+
});
|
|
168
|
+
this.#keycloakInstance.set(keycloak);
|
|
218
169
|
try {
|
|
219
|
-
|
|
220
|
-
await this.loadKeycloakScript();
|
|
221
|
-
const keycloak = new Keycloak(this.config);
|
|
222
|
-
this.#keycloakInstance.set(keycloak);
|
|
223
|
-
const initOptions = {
|
|
170
|
+
const authenticated = await keycloak.init({
|
|
224
171
|
onLoad: 'login-required',
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
...this.config.initOptions
|
|
228
|
-
};
|
|
229
|
-
const authenticated = await keycloak.init(initOptions);
|
|
172
|
+
redirectUri: this.config.options?.loginRedirect || window.location.origin
|
|
173
|
+
});
|
|
230
174
|
this.#isAuthenticated.set(authenticated);
|
|
231
175
|
if (authenticated) {
|
|
232
176
|
await this.loadUserProfile();
|
|
@@ -234,95 +178,65 @@ class KeycloakService {
|
|
|
234
178
|
return authenticated;
|
|
235
179
|
}
|
|
236
180
|
catch (error) {
|
|
237
|
-
|
|
181
|
+
console.error('Failed to initialize Keycloak:', error);
|
|
182
|
+
this.#keycloakInstance.set(null);
|
|
183
|
+
this.#isAuthenticated.set(false);
|
|
184
|
+
throw error;
|
|
238
185
|
}
|
|
239
186
|
}
|
|
240
|
-
/**
|
|
241
|
-
* Redirect to Keycloak login page
|
|
242
|
-
* @param options Additional login options
|
|
243
|
-
* @returns Promise resolving when login is complete
|
|
244
|
-
*/
|
|
187
|
+
/** Login with optional redirect URI */
|
|
245
188
|
login(options = {}) {
|
|
246
189
|
const instance = this.#keycloakInstance();
|
|
247
|
-
if (!instance)
|
|
248
|
-
throw new
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
...options
|
|
253
|
-
};
|
|
254
|
-
return instance.login(loginOptions);
|
|
190
|
+
if (!instance)
|
|
191
|
+
throw new Error('Keycloak not initialized');
|
|
192
|
+
return instance.login({
|
|
193
|
+
redirectUri: options.redirectUri || this.config.options?.loginRedirect || window.location.origin
|
|
194
|
+
});
|
|
255
195
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Logout the current user
|
|
258
|
-
* @returns Promise resolving when logout is complete
|
|
259
|
-
*/
|
|
196
|
+
/** Logout the current user */
|
|
260
197
|
logout() {
|
|
261
198
|
const instance = this.#keycloakInstance();
|
|
262
|
-
if (!instance)
|
|
263
|
-
throw new
|
|
264
|
-
}
|
|
199
|
+
if (!instance)
|
|
200
|
+
throw new Error('Keycloak not initialized');
|
|
265
201
|
return instance.logout();
|
|
266
202
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Get the current access token
|
|
269
|
-
* @returns Promise resolving to the access token or null
|
|
270
|
-
*/
|
|
271
|
-
async getToken() {
|
|
272
|
-
try {
|
|
273
|
-
await this.updateToken(this.config.tokenValidityInSeconds || 60);
|
|
274
|
-
return this.#keycloakInstance()?.token ?? null;
|
|
275
|
-
}
|
|
276
|
-
catch (error) {
|
|
277
|
-
throw new KeycloakError(KeycloakErrorType.TOKEN_REFRESH, 'Failed to get token', error);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Get the current user's roles
|
|
282
|
-
* @returns Array of role names
|
|
283
|
-
*/
|
|
203
|
+
/** Get roles for the current user */
|
|
284
204
|
getRoles() {
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
throw new KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
|
|
288
|
-
}
|
|
289
|
-
return keycloak.realmAccess?.roles ?? [];
|
|
205
|
+
const instance = this.#keycloakInstance();
|
|
206
|
+
return instance?.realmAccess?.roles ?? [];
|
|
290
207
|
}
|
|
291
|
-
/**
|
|
292
|
-
* Check if the current user has a specific role
|
|
293
|
-
* @param role Role name to check
|
|
294
|
-
* @returns true if user has the role
|
|
295
|
-
*/
|
|
208
|
+
/** Check if user has a specific role */
|
|
296
209
|
hasRole(role) {
|
|
297
210
|
return this.getRoles().includes(role);
|
|
298
211
|
}
|
|
299
|
-
/**
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
212
|
+
/** Get the current access token */
|
|
213
|
+
async getToken() {
|
|
214
|
+
const instance = this.#keycloakInstance();
|
|
215
|
+
if (!instance?.token)
|
|
216
|
+
return null;
|
|
217
|
+
const validityThreshold = this.config.options?.tokenValidityThreshold ?? 60;
|
|
218
|
+
await this.updateToken(validityThreshold);
|
|
219
|
+
return instance.token;
|
|
220
|
+
}
|
|
221
|
+
/** Update token if it's close to expiring */
|
|
222
|
+
async updateToken(minValidity) {
|
|
223
|
+
const instance = this.#keycloakInstance();
|
|
224
|
+
if (!instance?.token)
|
|
225
|
+
return false;
|
|
226
|
+
try {
|
|
227
|
+
return await instance.updateToken(minValidity);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
this.login(); // Redirect to login if token refresh fails
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
319
233
|
}
|
|
320
234
|
async loadUserProfile() {
|
|
235
|
+
const instance = this.#keycloakInstance();
|
|
236
|
+
if (!instance)
|
|
237
|
+
return;
|
|
321
238
|
try {
|
|
322
|
-
const
|
|
323
|
-
if (!keycloak)
|
|
324
|
-
return;
|
|
325
|
-
const profile = await keycloak.loadUserProfile();
|
|
239
|
+
const profile = await instance.loadUserProfile();
|
|
326
240
|
this.#userProfile.set(profile);
|
|
327
241
|
}
|
|
328
242
|
catch (error) {
|
|
@@ -338,55 +252,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
|
|
|
338
252
|
}] });
|
|
339
253
|
|
|
340
254
|
class KeycloakAuthInterceptor {
|
|
341
|
-
keycloakService;
|
|
342
|
-
|
|
343
|
-
this.keycloakService = keycloakService;
|
|
344
|
-
}
|
|
255
|
+
keycloakService = inject(KeycloakService);
|
|
256
|
+
config = inject(KEYCLOAK_CONFIG);
|
|
345
257
|
intercept(req, next) {
|
|
346
|
-
|
|
347
|
-
if (this.keycloakService.isUrlExcluded(req.url)) {
|
|
258
|
+
if (!this.config.options?.bearerToken || this.keycloakService.isUrlExcluded(req.url)) {
|
|
348
259
|
return next.handle(req);
|
|
349
260
|
}
|
|
350
261
|
return from(this.keycloakService.getToken()).pipe(mergeMap(token => {
|
|
351
|
-
if (token)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return next.handle(req);
|
|
262
|
+
if (!token)
|
|
263
|
+
return next.handle(req);
|
|
264
|
+
const authReq = req.clone({
|
|
265
|
+
headers: req.headers.set('Authorization', `Bearer ${token}`)
|
|
266
|
+
});
|
|
267
|
+
return next.handle(authReq);
|
|
358
268
|
}), catchError((error) => {
|
|
359
|
-
// Handle 401/403 errors by refreshing token or redirecting to login
|
|
360
269
|
if (error.status === 401 || error.status === 403) {
|
|
361
|
-
|
|
362
|
-
if (refreshed) {
|
|
363
|
-
return from(this.keycloakService.getToken()).pipe(mergeMap(newToken => {
|
|
364
|
-
const authReq = req.clone({
|
|
365
|
-
headers: req.headers.set('Authorization', `Bearer ${newToken}`)
|
|
366
|
-
});
|
|
367
|
-
return next.handle(authReq);
|
|
368
|
-
}));
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
// Token refresh failed, redirect to login
|
|
372
|
-
this.keycloakService.login();
|
|
373
|
-
return throwError(() => error);
|
|
374
|
-
}
|
|
375
|
-
}));
|
|
270
|
+
this.keycloakService.login();
|
|
376
271
|
}
|
|
377
272
|
return throwError(() => error);
|
|
378
273
|
}));
|
|
379
274
|
}
|
|
380
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor, deps: [
|
|
275
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
381
276
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor });
|
|
382
277
|
}
|
|
383
278
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor, decorators: [{
|
|
384
279
|
type: Injectable
|
|
385
|
-
}]
|
|
280
|
+
}] });
|
|
386
281
|
|
|
387
|
-
function initializeKeycloak(keycloak) {
|
|
388
|
-
return () => keycloak.initialize();
|
|
389
|
-
}
|
|
390
282
|
/**
|
|
391
283
|
* Provides Keycloak authentication and authorization services
|
|
392
284
|
* @param config Keycloak configuration
|
|
@@ -395,45 +287,45 @@ function initializeKeycloak(keycloak) {
|
|
|
395
287
|
function provideKeycloak(config) {
|
|
396
288
|
return [
|
|
397
289
|
importProvidersFrom(HttpClientModule),
|
|
398
|
-
{
|
|
399
|
-
provide: KEYCLOAK_CONFIG,
|
|
400
|
-
useValue: config
|
|
401
|
-
},
|
|
290
|
+
{ provide: KEYCLOAK_CONFIG, useValue: config },
|
|
402
291
|
KeycloakService,
|
|
403
292
|
{
|
|
404
293
|
provide: APP_INITIALIZER,
|
|
405
|
-
useFactory:
|
|
294
|
+
useFactory: (keycloak) => () => keycloak.initialize(),
|
|
406
295
|
multi: true,
|
|
407
296
|
deps: [KeycloakService]
|
|
408
297
|
},
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
298
|
+
// Only provide the interceptor if bearer token is enabled
|
|
299
|
+
...(config.options?.bearerToken ? [{
|
|
300
|
+
provide: HTTP_INTERCEPTORS,
|
|
301
|
+
useClass: KeycloakAuthInterceptor,
|
|
302
|
+
multi: true
|
|
303
|
+
}] : [])
|
|
414
304
|
];
|
|
415
305
|
}
|
|
416
306
|
|
|
417
307
|
class KeycloakAuthGuard {
|
|
418
308
|
keycloakService = inject(KeycloakService);
|
|
309
|
+
config = inject(KEYCLOAK_CONFIG);
|
|
419
310
|
router = inject(Router);
|
|
420
311
|
canActivate(route, state) {
|
|
421
312
|
return toObservable(this.keycloakService.isAuthenticated$).pipe(map(authenticated => {
|
|
422
313
|
if (!authenticated) {
|
|
423
|
-
this.keycloakService.login({
|
|
424
|
-
redirectUri: window.location.origin + state.url
|
|
425
|
-
});
|
|
314
|
+
this.keycloakService.login({ redirectUri: window.location.origin + state.url });
|
|
426
315
|
return false;
|
|
427
316
|
}
|
|
428
|
-
|
|
429
|
-
|
|
317
|
+
// Check for route-specific roles first, then fallback to default roles
|
|
318
|
+
const routeRoles = route.data['roles'];
|
|
319
|
+
const defaultRoles = this.config.options?.roles;
|
|
320
|
+
const requiredRoles = routeRoles || defaultRoles;
|
|
321
|
+
if (requiredRoles?.length && !this.hasRequiredRoles(requiredRoles)) {
|
|
430
322
|
return this.router.createUrlTree(['/unauthorized']);
|
|
431
323
|
}
|
|
432
324
|
return true;
|
|
433
325
|
}));
|
|
434
326
|
}
|
|
435
|
-
hasRequiredRoles(
|
|
436
|
-
return
|
|
327
|
+
hasRequiredRoles(roles) {
|
|
328
|
+
return roles.every(role => this.keycloakService.hasRole(role));
|
|
437
329
|
}
|
|
438
330
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
439
331
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, providedIn: 'root' });
|
|
@@ -3267,5 +3159,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
|
|
|
3267
3159
|
* Generated bundle index. Do not edit.
|
|
3268
3160
|
*/
|
|
3269
3161
|
|
|
3270
|
-
export { AppConstants, AppMessages, BaseCrudService, BaseFormComponent, BaseListPageComponent, BaseListTabbedFormComponent, BasePageComponent, BaseTabbedFormComponent, BaseTaskDashboardComponent, BaseTaskDetailsComponent, BaseTaskService, ComponentContext, CoreFormlyModule, FormService, FormlyConfigService, HttpUtilityService, IndianDatePipe, KEYCLOAK_CONFIG, KeycloakAuthGuard,
|
|
3162
|
+
export { AppConstants, AppMessages, BaseCrudService, BaseFormComponent, BaseListPageComponent, BaseListTabbedFormComponent, BasePageComponent, BaseTabbedFormComponent, BaseTaskDashboardComponent, BaseTaskDetailsComponent, BaseTaskService, ComponentContext, CoreFormlyModule, FormService, FormlyConfigService, HttpUtilityService, IndianDatePipe, KEYCLOAK_CONFIG, KeycloakAuthGuard, KeycloakService, NotificationService, TabbedFormType, TaskRole, UtilsService, provideKeycloak };
|
|
3271
3163
|
//# sourceMappingURL=codex-ts-core-lib.mjs.map
|