@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.
@@ -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.bearerExcludedUrls?.some(excluded => url.startsWith(excluded) || new RegExp(excluded).test(url)) ?? false;
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
- getSilentCheckUrl() {
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
- this.validateConfig();
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
- silentCheckSsoRedirectUri: this.getSilentCheckUrl(),
226
- checkLoginIframe: false,
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
- throw new KeycloakError(KeycloakErrorType.INIT, 'Failed to initialize Keycloak', error);
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 KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
249
- }
250
- const loginOptions = {
251
- ...this.config.loginOptions,
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 KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
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 keycloak = this.#keycloakInstance();
286
- if (!keycloak) {
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
- * Update the access token if it's close to expiring
301
- * @param minValidity Minimum validity time in seconds
302
- * @returns Promise resolving to true if token was refreshed
303
- */
304
- updateToken(minValidity = 60) {
305
- return new Promise((resolve, reject) => {
306
- const keycloak = this.#keycloakInstance();
307
- if (!keycloak?.token) {
308
- reject(new KeycloakError(KeycloakErrorType.TOKEN_REFRESH, 'No token available'));
309
- return;
310
- }
311
- keycloak.updateToken(minValidity)
312
- .then((refreshed) => {
313
- resolve(refreshed);
314
- })
315
- .catch((error) => {
316
- reject(new KeycloakError(KeycloakErrorType.TOKEN_REFRESH, 'Failed to refresh token', error));
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 keycloak = this.#keycloakInstance();
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
- constructor(keycloakService) {
343
- this.keycloakService = keycloakService;
344
- }
255
+ keycloakService = inject(KeycloakService);
256
+ config = inject(KEYCLOAK_CONFIG);
345
257
  intercept(req, next) {
346
- // Skip bearer token for excluded URLs
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
- const authReq = req.clone({
353
- headers: req.headers.set('Authorization', `Bearer ${token}`)
354
- });
355
- return next.handle(authReq);
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
- return from(this.keycloakService.updateToken()).pipe(mergeMap(refreshed => {
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: [{ token: KeycloakService }], target: i0.ɵɵFactoryTarget.Injectable });
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
- }], ctorParameters: () => [{ type: KeycloakService }] });
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: initializeKeycloak,
294
+ useFactory: (keycloak) => () => keycloak.initialize(),
406
295
  multi: true,
407
296
  deps: [KeycloakService]
408
297
  },
409
- {
410
- provide: HTTP_INTERCEPTORS,
411
- useClass: KeycloakAuthInterceptor,
412
- multi: true
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
- const requiredRoles = route.data['roles'];
429
- if (requiredRoles && !this.hasRequiredRoles(requiredRoles)) {
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(requiredRoles) {
436
- return requiredRoles.every(role => this.keycloakService.getRoles().includes(role));
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, KeycloakError, KeycloakErrorType, KeycloakService, NotificationService, TabbedFormType, TaskRole, UtilsService, provideKeycloak };
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