@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.
- package/fesm2022/codex-ts-core-lib.mjs +97 -201
- 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 +14 -53
- package/package.json +1 -1
|
@@ -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.
|
|
156
|
+
return this.config.options?.excludedUrls?.some(excluded => url.startsWith(excluded) || new RegExp(excluded).test(url)) ?? false;
|
|
176
157
|
}
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
if (!this.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
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
|
|
286
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
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
|
-
|
|
343
|
-
this.keycloakService = keycloakService;
|
|
344
|
-
}
|
|
259
|
+
keycloakService = inject(KeycloakService);
|
|
260
|
+
config = inject(KEYCLOAK_CONFIG);
|
|
345
261
|
intercept(req, next) {
|
|
346
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
})
|
|
355
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
-
}]
|
|
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:
|
|
298
|
+
useFactory: (keycloak) => () => keycloak.initialize(),
|
|
406
299
|
multi: true,
|
|
407
300
|
deps: [KeycloakService]
|
|
408
301
|
},
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
429
|
-
|
|
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(
|
|
436
|
-
return
|
|
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,
|
|
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
|