@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.
- package/fesm2022/codex-ts-core-lib.mjs +97 -199
- 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
- package/src/lib/assets/keycloak/silent-check-sso.html +0 -11
|
@@ -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.
|
|
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
|
-
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
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
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
|
|
284
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
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
|
-
|
|
341
|
-
this.keycloakService = keycloakService;
|
|
342
|
-
}
|
|
259
|
+
keycloakService = inject(KeycloakService);
|
|
260
|
+
config = inject(KEYCLOAK_CONFIG);
|
|
343
261
|
intercept(req, next) {
|
|
344
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
})
|
|
353
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
-
}]
|
|
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:
|
|
298
|
+
useFactory: (keycloak) => () => keycloak.initialize(),
|
|
404
299
|
multi: true,
|
|
405
300
|
deps: [KeycloakService]
|
|
406
301
|
},
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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(
|
|
434
|
-
return
|
|
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,
|
|
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
|