@codex-ts/core-lib 1.0.18 → 1.0.19

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,9 @@
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 * as i1 from '@angular/common/http';
6
+ import { HTTP_INTERCEPTORS, HttpClientModule, HttpParams } from '@angular/common/http';
7
7
  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
8
  import * as i1$3 from '@angular/router';
9
9
  import { Router } from '@angular/router';
@@ -138,30 +138,10 @@ var TaskRole;
138
138
 
139
139
  const KEYCLOAK_CONFIG = new InjectionToken('KEYCLOAK_CONFIG');
140
140
 
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
141
  /**
161
142
  * Service for handling Keycloak authentication and authorization
162
143
  */
163
144
  class KeycloakService {
164
- http = inject(HttpClient);
165
145
  platformId = inject(PLATFORM_ID);
166
146
  config = inject(KEYCLOAK_CONFIG);
167
147
  isBrowser = isPlatformBrowser(this.platformId);
@@ -170,159 +150,97 @@ class KeycloakService {
170
150
  #userProfile = signal(null);
171
151
  isAuthenticated$ = computed(() => this.#isAuthenticated());
172
152
  userProfile$ = computed(() => this.#userProfile());
153
+ token$ = computed(() => this.#keycloakInstance()?.token);
173
154
  /** Check if URL should be excluded from bearer token */
174
155
  isUrlExcluded(url) {
175
- return this.config.bearerExcludedUrls?.some(excluded => url.startsWith(excluded) || new RegExp(excluded).test(url)) ?? false;
156
+ return this.config.options?.excludedUrls?.some(excluded => url.startsWith(excluded) || new RegExp(excluded).test(url)) ?? false;
176
157
  }
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');
158
+ /** Initialize Keycloak authentication */
159
+ async initialize() {
160
+ if (!this.isBrowser)
161
+ return false;
162
+ await this.loadKeycloakScript();
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);
169
+ const authenticated = await keycloak.init({
170
+ onLoad: 'login-required',
171
+ redirectUri: this.config.options?.loginRedirect || window.location.origin
172
+ });
173
+ this.#isAuthenticated.set(authenticated);
174
+ if (authenticated) {
175
+ await this.loadUserProfile();
187
176
  }
177
+ return authenticated;
188
178
  }
189
- loadKeycloakScript() {
190
- if (!this.isBrowser)
191
- return Promise.resolve();
179
+ async loadKeycloakScript() {
192
180
  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;
181
+ return;
182
+ const script = document.createElement('script');
183
+ script.src = `${this.config.authServerUrl}/js/keycloak.js`;
184
+ script.async = true;
185
+ await new Promise((resolve, reject) => {
198
186
  script.onload = () => resolve();
199
187
  script.onerror = () => reject(new Error('Failed to load Keycloak script'));
200
188
  document.head.appendChild(script);
201
189
  });
202
190
  }
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
- */
215
- async initialize() {
216
- if (!this.isBrowser)
217
- return false;
218
- try {
219
- this.validateConfig();
220
- await this.loadKeycloakScript();
221
- const keycloak = new Keycloak(this.config);
222
- this.#keycloakInstance.set(keycloak);
223
- const initOptions = {
224
- onLoad: 'login-required',
225
- silentCheckSsoRedirectUri: this.getSilentCheckUrl(),
226
- checkLoginIframe: false,
227
- ...this.config.initOptions
228
- };
229
- const authenticated = await keycloak.init(initOptions);
230
- this.#isAuthenticated.set(authenticated);
231
- if (authenticated) {
232
- await this.loadUserProfile();
233
- }
234
- return authenticated;
235
- }
236
- catch (error) {
237
- throw new KeycloakError(KeycloakErrorType.INIT, 'Failed to initialize Keycloak', error);
238
- }
239
- }
240
- /**
241
- * Redirect to Keycloak login page
242
- * @param options Additional login options
243
- * @returns Promise resolving when login is complete
244
- */
191
+ /** Login with optional redirect URI */
245
192
  login(options = {}) {
246
193
  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);
194
+ if (!instance)
195
+ throw new Error('Keycloak not initialized');
196
+ return instance.login({
197
+ redirectUri: options.redirectUri || this.config.options?.loginRedirect || window.location.origin
198
+ });
255
199
  }
256
- /**
257
- * Logout the current user
258
- * @returns Promise resolving when logout is complete
259
- */
200
+ /** Logout the current user */
260
201
  logout() {
261
202
  const instance = this.#keycloakInstance();
262
- if (!instance) {
263
- throw new KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
264
- }
203
+ if (!instance)
204
+ throw new Error('Keycloak not initialized');
265
205
  return instance.logout();
266
206
  }
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
- */
207
+ /** Get roles for the current user */
284
208
  getRoles() {
285
- const keycloak = this.#keycloakInstance();
286
- if (!keycloak) {
287
- throw new KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
288
- }
289
- return keycloak.realmAccess?.roles ?? [];
209
+ const instance = this.#keycloakInstance();
210
+ return instance?.realmAccess?.roles ?? [];
290
211
  }
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
- */
212
+ /** Check if user has a specific role */
296
213
  hasRole(role) {
297
214
  return this.getRoles().includes(role);
298
215
  }
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
- });
216
+ /** Get the current access token */
217
+ async getToken() {
218
+ const instance = this.#keycloakInstance();
219
+ if (!instance?.token)
220
+ return null;
221
+ const validityThreshold = this.config.options?.tokenValidityThreshold ?? 60;
222
+ await this.updateToken(validityThreshold);
223
+ return instance.token;
224
+ }
225
+ /** Update token if it's close to expiring */
226
+ async updateToken(minValidity) {
227
+ const instance = this.#keycloakInstance();
228
+ if (!instance?.token)
229
+ return false;
230
+ try {
231
+ return await instance.updateToken(minValidity);
232
+ }
233
+ catch (error) {
234
+ this.login(); // Redirect to login if token refresh fails
235
+ return false;
236
+ }
319
237
  }
320
238
  async loadUserProfile() {
239
+ const instance = this.#keycloakInstance();
240
+ if (!instance)
241
+ return;
321
242
  try {
322
- const keycloak = this.#keycloakInstance();
323
- if (!keycloak)
324
- return;
325
- const profile = await keycloak.loadUserProfile();
243
+ const profile = await instance.loadUserProfile();
326
244
  this.#userProfile.set(profile);
327
245
  }
328
246
  catch (error) {
@@ -338,55 +256,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
338
256
  }] });
339
257
 
340
258
  class KeycloakAuthInterceptor {
341
- keycloakService;
342
- constructor(keycloakService) {
343
- this.keycloakService = keycloakService;
344
- }
259
+ keycloakService = inject(KeycloakService);
260
+ config = inject(KEYCLOAK_CONFIG);
345
261
  intercept(req, next) {
346
- // Skip bearer token for excluded URLs
347
- if (this.keycloakService.isUrlExcluded(req.url)) {
262
+ if (!this.config.options?.bearerToken || this.keycloakService.isUrlExcluded(req.url)) {
348
263
  return next.handle(req);
349
264
  }
350
265
  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);
266
+ if (!token)
267
+ return next.handle(req);
268
+ const authReq = req.clone({
269
+ headers: req.headers.set('Authorization', `Bearer ${token}`)
270
+ });
271
+ return next.handle(authReq);
358
272
  }), catchError((error) => {
359
- // Handle 401/403 errors by refreshing token or redirecting to login
360
273
  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
- }));
274
+ this.keycloakService.login();
376
275
  }
377
276
  return throwError(() => error);
378
277
  }));
379
278
  }
380
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor, deps: [{ token: KeycloakService }], target: i0.ɵɵFactoryTarget.Injectable });
279
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
381
280
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor });
382
281
  }
383
282
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor, decorators: [{
384
283
  type: Injectable
385
- }], ctorParameters: () => [{ type: KeycloakService }] });
284
+ }] });
386
285
 
387
- function initializeKeycloak(keycloak) {
388
- return () => keycloak.initialize();
389
- }
390
286
  /**
391
287
  * Provides Keycloak authentication and authorization services
392
288
  * @param config Keycloak configuration
@@ -395,45 +291,45 @@ function initializeKeycloak(keycloak) {
395
291
  function provideKeycloak(config) {
396
292
  return [
397
293
  importProvidersFrom(HttpClientModule),
398
- {
399
- provide: KEYCLOAK_CONFIG,
400
- useValue: config
401
- },
294
+ { provide: KEYCLOAK_CONFIG, useValue: config },
402
295
  KeycloakService,
403
296
  {
404
297
  provide: APP_INITIALIZER,
405
- useFactory: initializeKeycloak,
298
+ useFactory: (keycloak) => () => keycloak.initialize(),
406
299
  multi: true,
407
300
  deps: [KeycloakService]
408
301
  },
409
- {
410
- provide: HTTP_INTERCEPTORS,
411
- useClass: KeycloakAuthInterceptor,
412
- multi: true
413
- }
302
+ // Only provide the interceptor if bearer token is enabled
303
+ ...(config.options?.bearerToken ? [{
304
+ provide: HTTP_INTERCEPTORS,
305
+ useClass: KeycloakAuthInterceptor,
306
+ multi: true
307
+ }] : [])
414
308
  ];
415
309
  }
416
310
 
417
311
  class KeycloakAuthGuard {
418
312
  keycloakService = inject(KeycloakService);
313
+ config = inject(KEYCLOAK_CONFIG);
419
314
  router = inject(Router);
420
315
  canActivate(route, state) {
421
316
  return toObservable(this.keycloakService.isAuthenticated$).pipe(map(authenticated => {
422
317
  if (!authenticated) {
423
- this.keycloakService.login({
424
- redirectUri: window.location.origin + state.url
425
- });
318
+ this.keycloakService.login({ redirectUri: window.location.origin + state.url });
426
319
  return false;
427
320
  }
428
- const requiredRoles = route.data['roles'];
429
- if (requiredRoles && !this.hasRequiredRoles(requiredRoles)) {
321
+ // Check for route-specific roles first, then fallback to default roles
322
+ const routeRoles = route.data['roles'];
323
+ const defaultRoles = this.config.options?.roles;
324
+ const requiredRoles = routeRoles || defaultRoles;
325
+ if (requiredRoles?.length && !this.hasRequiredRoles(requiredRoles)) {
430
326
  return this.router.createUrlTree(['/unauthorized']);
431
327
  }
432
328
  return true;
433
329
  }));
434
330
  }
435
- hasRequiredRoles(requiredRoles) {
436
- return requiredRoles.every(role => this.keycloakService.getRoles().includes(role));
331
+ hasRequiredRoles(roles) {
332
+ return roles.every(role => this.keycloakService.hasRole(role));
437
333
  }
438
334
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
439
335
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, providedIn: 'root' });
@@ -3267,5 +3163,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
3267
3163
  * Generated bundle index. Do not edit.
3268
3164
  */
3269
3165
 
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 };
3166
+ 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
3167
  //# sourceMappingURL=codex-ts-core-lib.mjs.map