@codex-ts/core-lib 1.0.17 → 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,157 +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
- return `${window.location.origin}/node_modules/@codex-ts/core-lib/assets/keycloak/silent-check-sso.html`;
207
- }
208
- /**
209
- * Initialize Keycloak authentication
210
- * @returns Promise resolving to true if initialization is successful
211
- * @throws KeycloakError if initialization fails
212
- */
213
- async initialize() {
214
- if (!this.isBrowser)
215
- return false;
216
- try {
217
- this.validateConfig();
218
- await this.loadKeycloakScript();
219
- const keycloak = new Keycloak(this.config);
220
- this.#keycloakInstance.set(keycloak);
221
- const initOptions = {
222
- onLoad: 'login-required',
223
- silentCheckSsoRedirectUri: this.getSilentCheckUrl(),
224
- checkLoginIframe: false,
225
- ...this.config.initOptions
226
- };
227
- const authenticated = await keycloak.init(initOptions);
228
- this.#isAuthenticated.set(authenticated);
229
- if (authenticated) {
230
- await this.loadUserProfile();
231
- }
232
- return authenticated;
233
- }
234
- catch (error) {
235
- throw new KeycloakError(KeycloakErrorType.INIT, 'Failed to initialize Keycloak', error);
236
- }
237
- }
238
- /**
239
- * Redirect to Keycloak login page
240
- * @param options Additional login options
241
- * @returns Promise resolving when login is complete
242
- */
191
+ /** Login with optional redirect URI */
243
192
  login(options = {}) {
244
193
  const instance = this.#keycloakInstance();
245
- if (!instance) {
246
- throw new KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
247
- }
248
- const loginOptions = {
249
- ...this.config.loginOptions,
250
- ...options
251
- };
252
- 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
+ });
253
199
  }
254
- /**
255
- * Logout the current user
256
- * @returns Promise resolving when logout is complete
257
- */
200
+ /** Logout the current user */
258
201
  logout() {
259
202
  const instance = this.#keycloakInstance();
260
- if (!instance) {
261
- throw new KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
262
- }
203
+ if (!instance)
204
+ throw new Error('Keycloak not initialized');
263
205
  return instance.logout();
264
206
  }
265
- /**
266
- * Get the current access token
267
- * @returns Promise resolving to the access token or null
268
- */
269
- async getToken() {
270
- try {
271
- await this.updateToken(this.config.tokenValidityInSeconds || 60);
272
- return this.#keycloakInstance()?.token ?? null;
273
- }
274
- catch (error) {
275
- throw new KeycloakError(KeycloakErrorType.TOKEN_REFRESH, 'Failed to get token', error);
276
- }
277
- }
278
- /**
279
- * Get the current user's roles
280
- * @returns Array of role names
281
- */
207
+ /** Get roles for the current user */
282
208
  getRoles() {
283
- const keycloak = this.#keycloakInstance();
284
- if (!keycloak) {
285
- throw new KeycloakError(KeycloakErrorType.INIT, 'Keycloak not initialized');
286
- }
287
- return keycloak.realmAccess?.roles ?? [];
209
+ const instance = this.#keycloakInstance();
210
+ return instance?.realmAccess?.roles ?? [];
288
211
  }
289
- /**
290
- * Check if the current user has a specific role
291
- * @param role Role name to check
292
- * @returns true if user has the role
293
- */
212
+ /** Check if user has a specific role */
294
213
  hasRole(role) {
295
214
  return this.getRoles().includes(role);
296
215
  }
297
- /**
298
- * Update the access token if it's close to expiring
299
- * @param minValidity Minimum validity time in seconds
300
- * @returns Promise resolving to true if token was refreshed
301
- */
302
- updateToken(minValidity = 60) {
303
- return new Promise((resolve, reject) => {
304
- const keycloak = this.#keycloakInstance();
305
- if (!keycloak?.token) {
306
- reject(new KeycloakError(KeycloakErrorType.TOKEN_REFRESH, 'No token available'));
307
- return;
308
- }
309
- keycloak.updateToken(minValidity)
310
- .then((refreshed) => {
311
- resolve(refreshed);
312
- })
313
- .catch((error) => {
314
- reject(new KeycloakError(KeycloakErrorType.TOKEN_REFRESH, 'Failed to refresh token', error));
315
- });
316
- });
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
+ }
317
237
  }
318
238
  async loadUserProfile() {
239
+ const instance = this.#keycloakInstance();
240
+ if (!instance)
241
+ return;
319
242
  try {
320
- const keycloak = this.#keycloakInstance();
321
- if (!keycloak)
322
- return;
323
- const profile = await keycloak.loadUserProfile();
243
+ const profile = await instance.loadUserProfile();
324
244
  this.#userProfile.set(profile);
325
245
  }
326
246
  catch (error) {
@@ -336,55 +256,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
336
256
  }] });
337
257
 
338
258
  class KeycloakAuthInterceptor {
339
- keycloakService;
340
- constructor(keycloakService) {
341
- this.keycloakService = keycloakService;
342
- }
259
+ keycloakService = inject(KeycloakService);
260
+ config = inject(KEYCLOAK_CONFIG);
343
261
  intercept(req, next) {
344
- // Skip bearer token for excluded URLs
345
- if (this.keycloakService.isUrlExcluded(req.url)) {
262
+ if (!this.config.options?.bearerToken || this.keycloakService.isUrlExcluded(req.url)) {
346
263
  return next.handle(req);
347
264
  }
348
265
  return from(this.keycloakService.getToken()).pipe(mergeMap(token => {
349
- if (token) {
350
- const authReq = req.clone({
351
- headers: req.headers.set('Authorization', `Bearer ${token}`)
352
- });
353
- return next.handle(authReq);
354
- }
355
- 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);
356
272
  }), catchError((error) => {
357
- // Handle 401/403 errors by refreshing token or redirecting to login
358
273
  if (error.status === 401 || error.status === 403) {
359
- return from(this.keycloakService.updateToken()).pipe(mergeMap(refreshed => {
360
- if (refreshed) {
361
- return from(this.keycloakService.getToken()).pipe(mergeMap(newToken => {
362
- const authReq = req.clone({
363
- headers: req.headers.set('Authorization', `Bearer ${newToken}`)
364
- });
365
- return next.handle(authReq);
366
- }));
367
- }
368
- else {
369
- // Token refresh failed, redirect to login
370
- this.keycloakService.login();
371
- return throwError(() => error);
372
- }
373
- }));
274
+ this.keycloakService.login();
374
275
  }
375
276
  return throwError(() => error);
376
277
  }));
377
278
  }
378
- 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 });
379
280
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor });
380
281
  }
381
282
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthInterceptor, decorators: [{
382
283
  type: Injectable
383
- }], ctorParameters: () => [{ type: KeycloakService }] });
284
+ }] });
384
285
 
385
- function initializeKeycloak(keycloak) {
386
- return () => keycloak.initialize();
387
- }
388
286
  /**
389
287
  * Provides Keycloak authentication and authorization services
390
288
  * @param config Keycloak configuration
@@ -393,45 +291,45 @@ function initializeKeycloak(keycloak) {
393
291
  function provideKeycloak(config) {
394
292
  return [
395
293
  importProvidersFrom(HttpClientModule),
396
- {
397
- provide: KEYCLOAK_CONFIG,
398
- useValue: config
399
- },
294
+ { provide: KEYCLOAK_CONFIG, useValue: config },
400
295
  KeycloakService,
401
296
  {
402
297
  provide: APP_INITIALIZER,
403
- useFactory: initializeKeycloak,
298
+ useFactory: (keycloak) => () => keycloak.initialize(),
404
299
  multi: true,
405
300
  deps: [KeycloakService]
406
301
  },
407
- {
408
- provide: HTTP_INTERCEPTORS,
409
- useClass: KeycloakAuthInterceptor,
410
- multi: true
411
- }
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
+ }] : [])
412
308
  ];
413
309
  }
414
310
 
415
311
  class KeycloakAuthGuard {
416
312
  keycloakService = inject(KeycloakService);
313
+ config = inject(KEYCLOAK_CONFIG);
417
314
  router = inject(Router);
418
315
  canActivate(route, state) {
419
316
  return toObservable(this.keycloakService.isAuthenticated$).pipe(map(authenticated => {
420
317
  if (!authenticated) {
421
- this.keycloakService.login({
422
- redirectUri: window.location.origin + state.url
423
- });
318
+ this.keycloakService.login({ redirectUri: window.location.origin + state.url });
424
319
  return false;
425
320
  }
426
- const requiredRoles = route.data['roles'];
427
- 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)) {
428
326
  return this.router.createUrlTree(['/unauthorized']);
429
327
  }
430
328
  return true;
431
329
  }));
432
330
  }
433
- hasRequiredRoles(requiredRoles) {
434
- return requiredRoles.every(role => this.keycloakService.getRoles().includes(role));
331
+ hasRequiredRoles(roles) {
332
+ return roles.every(role => this.keycloakService.hasRole(role));
435
333
  }
436
334
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
437
335
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, providedIn: 'root' });
@@ -3265,5 +3163,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
3265
3163
  * Generated bundle index. Do not edit.
3266
3164
  */
3267
3165
 
3268
- 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 };
3269
3167
  //# sourceMappingURL=codex-ts-core-lib.mjs.map