@odx/auth 15.0.0 → 15.0.1

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/esm2022/lib/auth.component.mjs +6 -1
  3. package/esm2022/lib/auth.config.mjs +38 -1
  4. package/esm2022/lib/auth.directive.mjs +11 -1
  5. package/esm2022/lib/auth.guard.mjs +9 -1
  6. package/esm2022/lib/auth.interceptor.mjs +8 -1
  7. package/esm2022/lib/auth.providers.mjs +35 -1
  8. package/esm2022/lib/auth.service.mjs +159 -1
  9. package/esm2022/lib/components/auth-actions/auth-actions.component.mjs +10 -1
  10. package/esm2022/lib/components/auth-loading-screen/auth-loading-screen.component.mjs +15 -3
  11. package/esm2022/lib/directives/auth-action.directive.mjs +7 -1
  12. package/esm2022/lib/directives/sign-in.directive.mjs +20 -1
  13. package/esm2022/lib/directives/sign-out.directive.mjs +20 -1
  14. package/esm2022/lib/helpers/create-auth-host-url.mjs +13 -1
  15. package/esm2022/lib/helpers/create-inititals.mjs +18 -1
  16. package/esm2022/lib/helpers/handle-auth-error.mjs +13 -3
  17. package/esm2022/lib/helpers/handle-oauth-event.mjs +9 -1
  18. package/esm2022/lib/helpers/resolve-email.mjs +21 -1
  19. package/esm2022/lib/helpers/resolve-username.mjs +22 -1
  20. package/esm2022/lib/helpers/set-http-auth-header.mjs +10 -1
  21. package/esm2022/lib/helpers/user-language-loader.mjs +8 -1
  22. package/esm2022/lib/plugins/core-debug.plugin.mjs +10 -1
  23. package/esm2022/lib/plugins/core-identity.plugin.mjs +8 -1
  24. package/esm2022/lib/plugins/user-profile-link.plugin.mjs +10 -1
  25. package/esm2022/lib/unauth.guard.mjs +9 -1
  26. package/esm2022/plugins/service-connect/lib/helpers/build-service-connect-url.mjs +8 -1
  27. package/esm2022/plugins/service-connect/lib/helpers/has-roles-or-rights-handler.mjs +8 -1
  28. package/esm2022/plugins/service-connect/lib/helpers/has-roles-or-rights.mjs +8 -1
  29. package/esm2022/plugins/service-connect/lib/helpers/service-connect-plugin-factory.mjs +7 -1
  30. package/esm2022/plugins/service-connect/lib/service-connect-rights.directive.mjs +13 -1
  31. package/esm2022/plugins/service-connect/lib/service-connect-rights.guard.mjs +9 -1
  32. package/esm2022/plugins/service-connect/lib/service-connect-rights.plugin.mjs +9 -1
  33. package/esm2022/plugins/service-connect/lib/service-connect-user-language.plugin.mjs +15 -1
  34. package/esm2022/plugins/service-connect/lib/service-connect-user-profile.plugin.mjs +9 -1
  35. package/fesm2022/odx-auth-plugins-service-connect.mjs +77 -0
  36. package/fesm2022/odx-auth-plugins-service-connect.mjs.map +1 -1
  37. package/fesm2022/odx-auth.mjs +466 -5
  38. package/fesm2022/odx-auth.mjs.map +1 -1
  39. package/lib/auth.component.d.ts +5 -0
  40. package/lib/auth.config.d.ts +42 -0
  41. package/lib/auth.directive.d.ts +10 -0
  42. package/lib/auth.guard.d.ts +8 -0
  43. package/lib/auth.interceptor.d.ts +7 -0
  44. package/lib/auth.providers.d.ts +34 -0
  45. package/lib/auth.service.d.ts +156 -0
  46. package/lib/components/auth-actions/auth-actions.component.d.ts +9 -0
  47. package/lib/components/auth-loading-screen/auth-loading-screen.component.d.ts +13 -0
  48. package/lib/directives/auth-action.directive.d.ts +6 -0
  49. package/lib/directives/sign-in.directive.d.ts +19 -0
  50. package/lib/directives/sign-out.directive.d.ts +19 -0
  51. package/lib/helpers/create-auth-host-url.d.ts +12 -0
  52. package/lib/helpers/create-inititals.d.ts +17 -0
  53. package/lib/helpers/handle-auth-error.d.ts +10 -0
  54. package/lib/helpers/handle-oauth-event.d.ts +8 -0
  55. package/lib/helpers/resolve-email.d.ts +20 -0
  56. package/lib/helpers/resolve-username.d.ts +21 -0
  57. package/lib/helpers/set-http-auth-header.d.ts +9 -0
  58. package/lib/helpers/user-language-loader.d.ts +7 -0
  59. package/lib/plugins/core-debug.plugin.d.ts +9 -0
  60. package/lib/plugins/core-identity.plugin.d.ts +7 -0
  61. package/lib/plugins/user-profile-link.plugin.d.ts +9 -0
  62. package/lib/unauth.guard.d.ts +8 -0
  63. package/package.json +1 -1
  64. package/plugins/service-connect/lib/helpers/build-service-connect-url.d.ts +7 -0
  65. package/plugins/service-connect/lib/helpers/has-roles-or-rights-handler.d.ts +7 -0
  66. package/plugins/service-connect/lib/helpers/has-roles-or-rights.d.ts +7 -0
  67. package/plugins/service-connect/lib/helpers/service-connect-plugin-factory.d.ts +6 -0
  68. package/plugins/service-connect/lib/service-connect-rights.directive.d.ts +12 -0
  69. package/plugins/service-connect/lib/service-connect-rights.guard.d.ts +8 -0
  70. package/plugins/service-connect/lib/service-connect-rights.plugin.d.ts +8 -0
  71. package/plugins/service-connect/lib/service-connect-user-language.plugin.d.ts +14 -0
  72. package/plugins/service-connect/lib/service-connect-user-profile.plugin.d.ts +8 -0
@@ -1,7 +1,7 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { CommonModule, AsyncPipe, NgIf } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { inject, EnvironmentInjector, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, DestroyRef, InjectionToken, ENVIRONMENT_INITIALIZER, makeEnvironmentProviders, APP_INITIALIZER, Injectable, Directive, EventEmitter, Output, HostListener, input, NgModule } from '@angular/core';
4
+ import { inject, EnvironmentInjector, runInInjectionContext, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, DestroyRef, InjectionToken, ENVIRONMENT_INITIALIZER, makeEnvironmentProviders, APP_INITIALIZER, Injectable, Directive, EventEmitter, Output, HostListener, input, NgModule } from '@angular/core';
5
5
  import * as i2$1 from '@odx/angular/components/area-header';
6
6
  import { AreaHeaderModule } from '@odx/angular/components/area-header';
7
7
  import * as i7 from '@odx/angular/components/dropdown';
@@ -27,17 +27,45 @@ import * as i3 from '@odx/angular/components/icon';
27
27
  import { IconComponent } from '@odx/angular/components/icon';
28
28
  import { trigger, transition, useAnimation } from '@angular/animations';
29
29
  import { fadeOut } from '@odx/angular/animations';
30
- import * as i5 from '@odx/angular/components/button';
31
- import { ButtonComponent } from '@odx/angular/components/button';
32
30
  import { CircularProgressComponent } from '@odx/angular/components/circular-progress';
33
31
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
34
32
  import * as i3$1 from '@odx/angular/components/avatar';
35
33
  import * as i4 from '@odx/angular/components/action-group';
34
+ import * as i5 from '@odx/angular/components/button';
36
35
 
36
+ /**
37
+ * Creates an authentication host URL based on the provided environment and URL segments.
38
+ *
39
+ * @param {AuthEnvironment} environment - The authentication environment (e.g., 'development', 'production').
40
+ * @param {string[]} segments - Additional URL segments to append to the base URL.
41
+ * @returns {string} The constructed authentication host URL.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * createAuthHostUrl('dev', 'api', 'v1', 'users'); // returns 'https://dev-auth.odx.com/api/v1/users'
46
+ * ```
47
+ */
37
48
  function createAuthHostUrl(environment, ...segments) {
38
49
  return buildUrl(ODX_AUTH_HOSTS[environment], ...segments);
39
50
  }
40
51
 
52
+ /**
53
+ * Creates initials from a given string.
54
+ *
55
+ * This function takes a string input, removes any text within parentheses,
56
+ * trims any leading or trailing whitespace, and then splits the string into
57
+ * parts based on spaces. It then constructs initials using the first letter
58
+ * of the first and last parts of the string, converting them to uppercase.
59
+ *
60
+ * @param {string | null} value - The input string from which to create initials. It can be
61
+ * undefined or null, in which case an empty string is returned.
62
+ * @returns {string} - A string containing the initials derived from the input string.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * createInitials('John Smith'); // returns 'JS'
67
+ * ```
68
+ */
41
69
  function createInitials(value) {
42
70
  if (!value)
43
71
  return '';
@@ -53,10 +81,20 @@ function createInitials(value) {
53
81
  }, '');
54
82
  }
55
83
 
84
+ /**
85
+ * Handles authentication errors by executing a series of error handler functions.
86
+ *
87
+ * @param {AuthErrorHandlerFn[]} handlers - An array of functions that handle authentication errors.
88
+ * Each function is expected to take an `OAuthErrorEvent` as an argument.
89
+ *
90
+ * @returns A function that takes an `OAuthErrorEvent` and processes it using the provided handlers.
91
+ * The function will stop processing once a handler successfully handles the error without throwing.
92
+ * If a handler throws an error that is not an instance of `OAuthErrorEvent`, the original error is re-thrown.
93
+ */
56
94
  function handleAuthError(handlers) {
57
95
  const injector = inject(EnvironmentInjector);
58
96
  return (error) => {
59
- injector.runInContext(() => {
97
+ runInInjectionContext(injector, () => {
60
98
  for (const handler of handlers) {
61
99
  try {
62
100
  handler(error);
@@ -72,10 +110,38 @@ function handleAuthError(handlers) {
72
110
  };
73
111
  }
74
112
 
113
+ /**
114
+ * Handles OAuth events of a specific type by applying a provided handler function.
115
+ *
116
+ * @template {T} - The type of OAuth event.
117
+ * @param {T['type']} type - The type of the OAuth event to handle.
118
+ * @param {(event: T) => Promise<void>} handler - A function that takes an event of type T and returns a Promise that resolves to void.
119
+ * @returns {OperatorFunction<T, void>} - An OperatorFunction that filters events of the specified type and applies the handler function.
120
+ */
75
121
  function handleOAuthEvent(type, handler) {
76
122
  return (source$) => source$.pipe(filter((event) => event.type === type), switchMap((event) => handler(event)));
77
123
  }
78
124
 
125
+ /**
126
+ * Resolves the email address from the given identity claims.
127
+ *
128
+ * This function attempts to extract an email address from the provided
129
+ * `OdxAuth.RawIdentityClaims` object by checking the following properties
130
+ * in order:
131
+ * 1. `email` - if it is a string, it is returned.
132
+ * 2. `email_address` - if it is a string, it is returned.
133
+ * 3. `emails` - if it is an array and the first element is a string, the first element is returned.
134
+ *
135
+ * If none of these properties contain a valid string email address, an empty string is returned.
136
+ *
137
+ * @param {OdxAuth.RawIdentityClaims} claims - The identity claims object from which to resolve the email address.
138
+ * @returns {string} - The resolved email address as a string, or an empty string if no valid email address is found.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * resolveEmail({ email_address: 'email.address@mdn.com' }) // returns 'email.address@mdn.com';
143
+ * ```
144
+ */
79
145
  function resolveEmail(claims) {
80
146
  if (isString(claims['email'])) {
81
147
  return claims['email'];
@@ -89,6 +155,27 @@ function resolveEmail(claims) {
89
155
  return '';
90
156
  }
91
157
 
158
+ /**
159
+ * Resolves the username from the provided identity claims.
160
+ *
161
+ * The function attempts to construct a username from the claims in the following order:
162
+ * 1. If both 'firstname' and 'lastname' are strings, it returns them concatenated with a space.
163
+ * 2. If both 'given_name' and 'family_name' are strings, it returns them concatenated with a space.
164
+ * 3. If 'name' is a string, it returns the 'name'.
165
+ * 4. If 'displayname' is a string, it returns the 'displayname'.
166
+ * 5. If none of the above conditions are met, it returns an empty string.
167
+ *
168
+ * @param {OdxAuth.RawIdentityClaims} claims - The raw identity claims from which to resolve the username.
169
+ * @returns {string} - The resolved username as a string.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * resolveUsername({ firstname: 'John', lastname: 'Doe' }) // returns 'John Doe';
174
+ * resolveUsername({ given_name: 'John', family_name: 'Doe' }) // returns 'John Doe';
175
+ * resolveUsername({ name: 'John Doe' }) // returns 'John Doe';
176
+ * resolveUsername({ displayname: 'John Doe' }) // returns 'John Doe';
177
+ * ```
178
+ */
92
179
  function resolveUsername(claims) {
93
180
  if (isString(claims['firstname']) && isString(claims['lastname'])) {
94
181
  return `${claims['firstname']} ${claims['lastname']}`;
@@ -105,6 +192,15 @@ function resolveUsername(claims) {
105
192
  return '';
106
193
  }
107
194
 
195
+ /**
196
+ * Sets the HTTP Authorization header for a given request.
197
+ *
198
+ * @template R - The type of the request, which extends HttpRequest<T> or Request.
199
+ * @template T - The type of the request body.
200
+ * @param {R} req - The HTTP request to which the Authorization header will be added.
201
+ * @param {string | null} [token] - The token to be used in the Authorization header. If no token is provided, the request is returned unchanged.
202
+ * @returns {R} - The modified HTTP request with the Authorization header set, or the original request if no token is provided.
203
+ */
108
204
  function setHttpAuthHeader(req, token) {
109
205
  if (!token)
110
206
  return req;
@@ -119,6 +215,13 @@ function setHttpAuthHeader(req, token) {
119
215
  const AuthEnvironment = Environment;
120
216
 
121
217
  const requireAuthentication = new HttpContextToken(() => false);
218
+ /**
219
+ * Interceptor to handle authentication for HTTP requests.
220
+ *
221
+ * This interceptor checks if the request URL is allowed or if the request requires authentication.
222
+ * If the URL is not allowed and the request does not require authentication, it simply forwards the request.
223
+ * Otherwise, it prepares the authentication request using the AuthService.
224
+ */
122
225
  const authInterceptor = (req, next) => {
123
226
  const { allowedUrls, requireSignInForRequests } = injectAuthConfig();
124
227
  const isUrlAllowed = allowedUrls.some((allowedUrl) => matchUrl(req.url, allowedUrl));
@@ -130,9 +233,18 @@ const authInterceptor = (req, next) => {
130
233
 
131
234
  var logger = new Logger('@odx/auth');
132
235
 
236
+ /**
237
+ * Displays authentication actions.
238
+ */
133
239
  let AuthActionsComponent = class AuthActionsComponent {
134
240
  constructor() {
135
241
  this.element = injectElement();
242
+ /**
243
+ * The identity claims.
244
+ *
245
+ * @type {OdxAuth.IdentiyClaims | null}
246
+ * @default null
247
+ */
136
248
  this.claims = null;
137
249
  }
138
250
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: AuthActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -148,12 +260,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImpo
148
260
  type: Input
149
261
  }] } });
150
262
 
263
+ /**
264
+ * Authentication loading screen.
265
+ *
266
+ * This component displays a loading screen with animations and dynamic content
267
+ * based on the authentication state.
268
+ */
151
269
  class AuthLoadingScreenComponent {
152
270
  constructor() {
153
271
  this.authConfig = injectAuthConfig();
154
272
  this.icon$ = inject(AuthService).isRedirecting$.pipe(map((isRedirecting) => (isRedirecting ? 'link-external' : 'user')));
155
273
  }
156
274
  static { this.instance = null; }
275
+ /**
276
+ * Initializes the authentication loading screen.
277
+ *
278
+ * @param {AuthService} authService - The authentication service.
279
+ * @param {DynamicViewService} dynamicViewService - The dynamic view service used to create the loading screen.
280
+ * @static
281
+ */
157
282
  static initialize(authService, dynamicViewService) {
158
283
  authService.isLoading$.subscribe((isLoading) => {
159
284
  if (isLoading) {
@@ -170,11 +295,20 @@ class AuthLoadingScreenComponent {
170
295
  }
171
296
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: AuthLoadingScreenComponent, decorators: [{
172
297
  type: Component,
173
- args: [{ standalone: true, selector: 'div.odx-auth-loading-screen', imports: [CommonModule, ButtonComponent, IconComponent, LogoDirective, CircularProgressComponent, DynamicViewDirective], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
298
+ args: [{ standalone: true, selector: 'div.odx-auth-loading-screen', imports: [CommonModule, IconComponent, LogoDirective, CircularProgressComponent, DynamicViewDirective], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
174
299
  '[@hostAnimation]': 'true',
175
300
  }, animations: [trigger('hostAnimation', [transition(':leave', useAnimation(fadeOut()))])], template: "<div class=\"odx-auth-loading-screen__content\" odxLayout=\"grid 12 horizontal-center vertical-center gap-small\">\n <odx-logo size=\"large\" />\n <odx-circular-progress class=\"odx-auth-loading-screen__spinner\" value=\"-1\" size=\"medium\" stroke=\"3\">\n <odx-icon [name]=\"icon$ | async\" iconSet=\"core\" />\n </odx-circular-progress>\n @if (authConfig.loadingScreenMessage; as content) {\n <p class=\"odx-auth-loading-screen__message\">\n <ng-template [odxDynamicView]=\"content\" />\n </p>\n }\n</div>\n", styles: ["@keyframes odx-auth-loading-screen-animation{0%{opacity:0;transform:translate(-50%,-50%) scale(.9)}to{opacity:1;transform:translate(-50%,-50%)}}.odx-auth-loading-screen{--odx-c-highlight: var(--odx-c-primary);background-color:var(--odx-c-background-content);inset:0;position:fixed;z-index:var(--odx-v-layer-6)}.odx-auth-loading-screen__content{animation:odx-auth-loading-screen-animation .75s ease;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.odx-auth-loading-screen__message{text-align:center}.odx-auth-loading-screen__spinner{position:relative}.odx-auth-loading-screen__spinner .odx-icon{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}\n"] }]
176
301
  }] });
177
302
 
303
+ /**
304
+ * A factory function that creates a core debug plugin for the authentication service.
305
+ * This plugin logs detailed information about the user's identity claims and tokens.
306
+ *
307
+ * @remarks
308
+ * This plugin is intended for debugging purposes only and should not be used in production environments.
309
+ *
310
+ * @returns {AuthPluginFactory} A function that takes an authentication service and sets up logging for identity claims and tokens.
311
+ */
178
312
  const coreDebugPlugin = () => {
179
313
  logger.warn('DEBUG MODE ENABLED - DO NOT USE IN PRODUCTION!');
180
314
  const destroyRef = inject(DestroyRef);
@@ -191,6 +325,13 @@ const coreDebugPlugin = () => {
191
325
  };
192
326
  };
193
327
 
328
+ /**
329
+ * Core identity plugin for authentication.
330
+ *
331
+ * This plugin extracts and processes identity claims from the authentication service.
332
+ *
333
+ * @returns {AuthPluginFactory} A factory function that returns an observable with the processed identity claims.
334
+ */
194
335
  const coreIdentityPlugin = () => {
195
336
  const { resolveEmail, resolveUsername, createInitials } = injectAuthConfig();
196
337
  return (authService) => {
@@ -202,6 +343,15 @@ const coreIdentityPlugin = () => {
202
343
  };
203
344
  };
204
345
 
346
+ /**
347
+ * A plugin factory that generates a user profile URL plugin.
348
+ *
349
+ * This plugin retrieves the user profile URL from the authentication configuration.
350
+ * If the user profile URL is not specified in the configuration, it falls back to
351
+ * using the default URL for the current environment from `ODX_AUTH_USER_PROFILE_HOSTS`.
352
+ *
353
+ * @returns A function that returns an observable emitting an object containing the user profile URL.
354
+ */
205
355
  const userProfileUrlPlugin = () => {
206
356
  const { environment, userProfileUrl } = injectAuthConfig();
207
357
  return () => {
@@ -239,6 +389,11 @@ const ODX_AUTH_PLUGINS = new InjectionToken('@odx/auth::Plugins', {
239
389
  return [...corePlugins, ...plugins].map((pluginFactory) => pluginFactory());
240
390
  },
241
391
  });
392
+ /**
393
+ * Provides a logger for the authentication module.
394
+ *
395
+ * @returns {Provider} - The provider for the logger.
396
+ */
242
397
  function provideAuthLogger() {
243
398
  return {
244
399
  provide: ENVIRONMENT_INITIALIZER,
@@ -251,11 +406,19 @@ function provideAuthLogger() {
251
406
  multi: true,
252
407
  };
253
408
  }
409
+ /**
410
+ * Initializes the authentication error handlers.
411
+ */
254
412
  function initializeAuthErrorHandlers() {
255
413
  const authService = inject(AuthService);
256
414
  const handler = handleAuthError(inject(ODX_AUTH_ERROR_HANDLERS));
257
415
  authService.errors$.pipe(tap((error) => handler(error))).subscribe();
258
416
  }
417
+ /**
418
+ * Initializes the authentication configuration.
419
+ *
420
+ * @returns {() => Promise<void>} - A function that returns a promise resolving when the configuration is initialized.
421
+ */
259
422
  function initalizeAuthConfig() {
260
423
  const { clientId, scopes, redirectPath, environment, postLogoutRedirectUrl, issuer, timeoutFactor, discoveryUrl, enableLoadingScreen } = injectAuthConfig();
261
424
  const authService = inject(AuthService);
@@ -278,6 +441,27 @@ function initalizeAuthConfig() {
278
441
  waitForTokenInMsec: 1000,
279
442
  });
280
443
  }
444
+ /**
445
+ * Provides the authentication configuration and dependencies.
446
+ *
447
+ * @param {ConfigProvider<Partial<AuthConfig>, D>} config - The configuration provider.
448
+ * @returns {EnvironmentProviders} The environment providers for authentication.
449
+ * @template D
450
+ *
451
+ * @example Provide the authentication configuration and dependencies
452
+ * ```ts
453
+ * providers: [
454
+ * provideAuth({
455
+ * useFactory: ({ environment, auth: { clientId, loadUserProfile } }: ApplicationEnvironment) => ({
456
+ * environment,
457
+ * clientId,
458
+ * loadUserProfile,
459
+ * }),
460
+ * deps: [APPLICATION_ENVIRONMENT],
461
+ * }),
462
+ * ],
463
+ * ```
464
+ */
281
465
  function provideAuth(config) {
282
466
  return makeEnvironmentProviders([
283
467
  provideAuthConfig(config),
@@ -345,7 +529,40 @@ const offlineAuthErrorHandler = (error) => {
345
529
  throw error;
346
530
  };
347
531
 
532
+ /**
533
+ * The `AuthService` class provides authentication functionality for an Angular application.
534
+ * It handles OAuth2/OIDC authentication, token management, and user identity claims.
535
+ *
536
+ * Key responsibilities include:
537
+ * - Initializing authentication with a provided configuration.
538
+ * - Managing tokens (access, refresh, and ID tokens).
539
+ * - Checking and emitting authentication and authorization states.
540
+ * - Handling user login and logout flows.
541
+ * - Supporting silent refresh and offline authentication scenarios.
542
+ * - Integrating authentication plugins via `AuthPluginManager`.
543
+ *
544
+ * @example
545
+ * ```typescript
546
+ * // Injecting the AuthService
547
+ * constructor(private authService: AuthService) {}
548
+ *
549
+ * // Using the AuthService to initialize authentication
550
+ * async ngOnInit() {
551
+ * const config: AuthConfig = { clientId: 'your-client-id', discoveryUrl: 'https://example.com/.well-known/openid-configuration' };
552
+ * await this.authService.initialize(config);
553
+ * }
554
+ * ```
555
+ */
348
556
  class AuthService {
557
+ /**
558
+ * Emits whenever the `access_token` in local storage is updated or cleared.
559
+ * Provides an observable for tracking token updates.
560
+ *
561
+ * @type {Observable<StorageEvent | null>}
562
+ */
563
+ get accessTokenUpdate$() {
564
+ return this.onAccessTokenUpdate$;
565
+ }
349
566
  constructor() {
350
567
  this.authConfig = injectAuthConfig();
351
568
  this.authPluginManager = inject(AuthPluginManager);
@@ -357,16 +574,62 @@ class AuthService {
357
574
  this.onAccessTokenUpdate$ = fromEvent(this.windowRef.nativeWindow, 'storage').pipe(filter(({ key }) => key === 'access_token' || key === null), startWith(null), share());
358
575
  this.silentRefreshHandler$ = this.isInitialized$$.pipe(filter(Boolean), switchMap(() => this.windowRef.isOnline$), tap((isOnline) => this.updateSilentRefresh(isOnline)));
359
576
  this.onAuthStateChange$ = combineLatest([this.oauthService.events.pipe(startWith(null)), this.windowRef.isOnline$, this.onAccessTokenUpdate$]);
577
+ /**
578
+ * Emits `true` when the service has completed initialization.
579
+ * Emits `false` until the initialization is complete.
580
+ *
581
+ * @type {Observable<boolean>}
582
+ */
360
583
  this.isInitialized$ = this.isInitialized$$.pipe(filter(Boolean), distinctUntilChanged());
584
+ /**
585
+ * Emits `true` when the user is being redirected to the login page.
586
+ * Emits `false` when there is no redirection in progress.
587
+ *
588
+ * @type {Observable<boolean>}
589
+ */
361
590
  this.isRedirecting$ = this.isRedirecting$$.pipe(distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
591
+ /**
592
+ * Emits `true` when the application is in a loading state (e.g., during redirection or plugin initialization).
593
+ * Emits `false` when there is no ongoing loading activity.
594
+ *
595
+ * @type {Observable<boolean>}
596
+ */
362
597
  this.isLoading$ = merge(this.isRedirecting$, this.authPluginManager.pluginsLoading$).pipe(distinctUntilChanged());
598
+ /**
599
+ * Emits `true` when the user is authenticated.
600
+ * Emits `false` when the user is not authenticated.
601
+ *
602
+ * @type {Observable<boolean>}
603
+ */
363
604
  this.isAuthenticated$ = this.isInitialized$.pipe(switchMap(() => this.authPluginManager.pluginsReady$), switchMap(() => this.onAuthStateChange$), map(() => this.isAuthenticated()), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
605
+ /**
606
+ * Emits the identity claims of the authenticated user.
607
+ * If the user is not authenticated, emits `null`.
608
+ *
609
+ * @type {Observable<OdxAuth.IdentityClaims | null>}
610
+ */
364
611
  this.identityClaims$ = this.isAuthenticated$.pipe(map(() => this.getIdentityClaims()), shareReplay({ bufferSize: 1, refCount: true }));
612
+ /**
613
+ * Emits OAuth error events.
614
+ *
615
+ * @type {Observable<OAuthErrorEvent>}
616
+ */
365
617
  this.errors$ = this.oauthService.events.pipe(filter((event) => event instanceof OAuthErrorEvent), share());
618
+ /**
619
+ * Emits events when an OAuth token is successfully received.
620
+ *
621
+ * @type {Observable<Event>}
622
+ */
366
623
  this.onTokenReceived$ = this.oauthService.events.pipe(filter((event) => event.type === 'token_received'), share());
367
624
  this.runPlugins();
368
625
  this.silentRefreshHandler$.subscribe();
369
626
  }
627
+ /**
628
+ * Initializes the authentication service with the provided configuration.
629
+ *
630
+ * @param {AuthConfig} config - The authentication configuration object.
631
+ * @returns {Promise<void>} Resolves when initialization is complete.
632
+ */
370
633
  async initialize(config) {
371
634
  this.assertAudience(config.clientId);
372
635
  this.oauthService.configure({ ...config, openUri: this.redirectToLogin.bind(this) });
@@ -393,57 +656,136 @@ class AuthService {
393
656
  await this.tryLoadUserProfile();
394
657
  await this.routeToRequestedUrl();
395
658
  }
659
+ /**
660
+ * Runs all authentication plugins registered in the `AuthPluginManager`.
661
+ */
396
662
  runPlugins() {
397
663
  this.authPluginManager.runPlugins(this).subscribe();
398
664
  }
665
+ /**
666
+ * Returns the issuer URL for the OAuth server.
667
+ *
668
+ * @returns {URL} The issuer URL.
669
+ */
399
670
  getIssuer() {
400
671
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
401
672
  return new URL(this.oauthService.issuer);
402
673
  }
674
+ /**
675
+ * Initiates the login flow for the user. Redirects to the login page.
676
+ *
677
+ * @param {string} [url] - The URL to redirect back to after login.
678
+ */
403
679
  signIn(url) {
404
680
  this.oauthService.initLoginFlow(url);
405
681
  }
682
+ /**
683
+ * Logs the user out and clears their tokens.
684
+ *
685
+ * @param {boolean} [noRedirect] - If `true`, no redirection occurs after logout.
686
+ */
406
687
  signOut(noRedirect) {
407
688
  this.oauthService.logOut(noRedirect || !this.getAccessToken());
408
689
  }
690
+ /**
691
+ * Attempts to refresh the user's tokens.
692
+ *
693
+ * @returns {Promise<TokenResponse>} Resolves with the new token response.
694
+ */
409
695
  async refreshTokens() {
410
696
  return this.oauthService.refreshToken();
411
697
  }
698
+ /**
699
+ * Retrieves the current access token, if available.
700
+ *
701
+ * @returns {string | null} The access token, or `null` if not available.
702
+ */
412
703
  getAccessToken() {
413
704
  return this.oauthService.getAccessToken() ?? null;
414
705
  }
706
+ /**
707
+ * Retrieves the current refresh token, if available.
708
+ *
709
+ * @returns {string | null} The refresh token, or `null` if not available.
710
+ */
415
711
  getRefreshToken() {
416
712
  return this.oauthService.getRefreshToken() ?? null;
417
713
  }
714
+ /**
715
+ * Retrieves the current ID token, if available.
716
+ *
717
+ * @returns {string | null} The ID token, or `null` if not available.
718
+ */
418
719
  getIdToken() {
419
720
  return this.oauthService.getIdToken() ?? null;
420
721
  }
722
+ /**
723
+ * Retrieves the identity claims of the authenticated user.
724
+ *
725
+ * @returns {OdxAuth.IdentityClaims | null} The identity claims, or `null` if not available.
726
+ */
421
727
  getIdentityClaims() {
422
728
  if (!this.getIdToken())
423
729
  return null;
424
730
  return deepmerge(this.oauthService.getIdentityClaims(), this.authPluginManager.getResult());
425
731
  }
732
+ /**
733
+ * Retrieves the raw identity claims of the authenticated user.
734
+ *
735
+ * @returns {OdxAuth.RawIdentityClaims | null} The raw identity claims, or `null` if not available.
736
+ */
426
737
  getRawIdentityClaims() {
427
738
  if (!this.getIdToken())
428
739
  return null;
429
740
  return this.oauthService.getIdentityClaims();
430
741
  }
742
+ /**
743
+ * Checks if the user is currently authenticated.
744
+ *
745
+ * @returns {boolean} `true` if authenticated, otherwise `false`.
746
+ */
431
747
  isAuthenticated() {
432
748
  if (this.windowRef.isOnline()) {
433
749
  return this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken();
434
750
  }
435
751
  return this.hasValidOfflineToken();
436
752
  }
753
+ /**
754
+ * Checks if the user is authorized based on the provided handler.
755
+ *
756
+ * @param {AuthorizedHandler | null} [authorizedHandler] - A handler to determine authorization.
757
+ * @returns {boolean} `true` if authorized, otherwise `false`.
758
+ */
437
759
  isAuthorized(authorizedHandler) {
438
760
  const handler = authorizedHandler ?? this.authConfig.defaultAuthorizedHandler;
439
761
  return this.isAuthenticated() && (handler?.(this.getIdentityClaims(), this, this.router) ?? true);
440
762
  }
763
+ /**
764
+ * Emits whether the user is authorized based on the provided handler.
765
+ *
766
+ * @param {AuthorizedHandler | null} [authorizedHandler] - A handler to determine authorization.
767
+ * @returns {Observable<boolean>} An observable emitting the authorization status.
768
+ */
441
769
  isAuthorized$(authorizedHandler) {
442
770
  return this.isAuthenticated$.pipe(map(() => this.isAuthorized(authorizedHandler)));
443
771
  }
772
+ /**
773
+ * Prepares an HTTP request by adding the access token to its headers.
774
+ *
775
+ * @param {HttpRequest<T> | Request} req - The HTTP request to prepare.
776
+ * @param {boolean} requireSignIn - Whether to require the user to sign in if no token is available.
777
+ * @returns {Observable<R>} An observable emitting the prepared request.
778
+ * @template R, T
779
+ */
444
780
  prepareAuthRequest$(req, requireSignIn = false) {
445
781
  return this.waitForAccessToken$(requireSignIn).pipe(map((token) => setHttpAuthHeader(req, token)));
446
782
  }
783
+ /**
784
+ * Waits for a valid access token to become available.
785
+ *
786
+ * @param {boolean} requireSignIn - Whether to require the user to sign in if no token is available.
787
+ * @returns {Observable<string | null>} An observable emitting the access token.
788
+ */
447
789
  waitForAccessToken$(requireSignIn) {
448
790
  const accessToken$ = of(this.getAccessToken()).pipe(filter((token) => !!token && this.isAuthenticated()));
449
791
  const waitForAccessToken$ = this.onTokenReceived$.pipe(timeout(this.authConfig.waitForTokenInMs ?? 0), catchError(async () => this.tryRefreshToken().catch(() => null)));
@@ -494,6 +836,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImpo
494
836
  args: [{ providedIn: 'root' }]
495
837
  }], ctorParameters: () => [] });
496
838
 
839
+ /**
840
+ * A function that creates a language loader function for user authentication.
841
+ *
842
+ * @param {LanguageSelector} languageSelector - A function that takes optional identity claims and returns a language string, null, or undefined.
843
+ * Defaults to a function that returns the preferred language from the claims.
844
+ * @returns {LanguageLoaderFn} - A function that retrieves the user's preferred language from the identity claims.
845
+ */
497
846
  function userLanguageLoader(languageSelector = (claims) => claims?.preferredLanguage) {
498
847
  return () => {
499
848
  return inject(AuthService).identityClaims$.pipe(map(languageSelector), map((value) => (isNonEmptyString(value) ? value : undefined)));
@@ -512,6 +861,43 @@ const ODX_AUTH_USER_PROFILE_HOSTS = {
512
861
  stage: 'https://purple-cliff-0e61c5703.3.azurestaticapps.net',
513
862
  prod: 'https://id.draeger.com',
514
863
  };
864
+ /**
865
+ * Tools for injecting and providing the auth configuration with default configuration for the authentication.
866
+ *
867
+ * @example
868
+ * // Providing custom authentication configuration.
869
+ * ```ts
870
+ * import { createInitials, resolveEmail, resolveUsername } from './helpers';
871
+ *
872
+ * providers: [provideAuthConfig({
873
+ * environment: 'dev',
874
+ * redirectPath: 'login/callback',
875
+ * allowedUrls: [],
876
+ * timeoutFactor: 0.75,
877
+ * maxOfflineTime: 60 * 60, // 1 hour
878
+ * loadUserProfile: false,
879
+ * errorHandler: (error) => {
880
+ * throw error;
881
+ * },
882
+ * createInitials,
883
+ * resolveEmail,
884
+ * resolveUsername,
885
+ * plugins: [],
886
+ * defaultAuthorizedHandler: null,
887
+ * enableLoadingScreen: true,
888
+ * loadingScreenMessage: 'Loading...',
889
+ * waitForTokenInMs: 500,
890
+ *
891
+ * })],
892
+ *
893
+ * // Injecting the datepicker configuration.
894
+ * ```ts
895
+ * @Component({})
896
+ * export class MyComponent {
897
+ * constructor(@Inject(injectAuthConfig()) private readonly authConfig: AuthConfig) {}
898
+ * }
899
+ * ```
900
+ */
515
901
  const { AuthDefaultConfig, AuthConfig, injectAuthConfig, provideAuthConfig } = createConfigTokens('Auth', '@odx/auth', {
516
902
  environment: 'prod',
517
903
  redirectPath: 'login/callback',
@@ -545,6 +931,12 @@ var translations = {
545
931
  },
546
932
  };
547
933
 
934
+ /**
935
+ * An abstract directive that integrates with the `LoadingSpinnerDirective` and `AuthService` to manage loading states based on authentication redirection.
936
+ *
937
+ * This directive automatically sets the `autoColor` property of the `LoadingSpinnerDirective` to `true` and subscribes to the `isRedirecting$` observable from the `AuthService`.
938
+ * When `isRedirecting$` emits a value, it updates the `isLoading` property of the `LoadingSpinnerDirective`.
939
+ */
548
940
  class AuthActionDirective {
549
941
  constructor() {
550
942
  this.takeUntilDestroyed = untilDestroyed();
@@ -564,9 +956,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImpo
564
956
  type: Directive
565
957
  }] });
566
958
 
959
+ /**
960
+ * A directive that handles the sign-in action for a button element.
961
+ *
962
+ * This directive extends the `AuthActionDirective` and uses the `LoadingSpinnerDirective`
963
+ * to show a loading spinner during the sign-in process.
964
+ *
965
+ * @see {AuthActionDirective}
966
+ * @see {LoadingSpinnerDirective}
967
+ *
968
+ * @example
969
+ * ```html
970
+ * <button odxButton odxAuthSignIn (odxAuthSignIn)="onSignIn()">Sign In</button>
971
+ * ```
972
+ */
567
973
  class SignInDirective extends AuthActionDirective {
568
974
  constructor() {
569
975
  super(...arguments);
976
+ /**
977
+ * Emits an event after the sign-in action is completed.
978
+ *
979
+ * @type {EventEmitter<void>}
980
+ */
570
981
  // eslint-disable-next-line @angular-eslint/no-output-rename
571
982
  this.afterSignIn = new EventEmitter();
572
983
  }
@@ -592,9 +1003,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImpo
592
1003
  args: ['click']
593
1004
  }] } });
594
1005
 
1006
+ /**
1007
+ * A directive that handles the sign-out action for a button element.
1008
+ *
1009
+ * This directive extends the `AuthActionDirective` and uses the `LoadingSpinnerDirective`
1010
+ * to show a loading spinner during the sign-out process.
1011
+ *
1012
+ * @see {AuthActionDirective}
1013
+ * @see {LoadingSpinnerDirective}
1014
+ *
1015
+ * @example
1016
+ * ```html
1017
+ * <button odxButton odxAuthSignOut (odxAuthSignOut)="onSignOut()">Sign Out</button>
1018
+ * ```
1019
+ */
595
1020
  class SignOutDirective extends AuthActionDirective {
596
1021
  constructor() {
597
1022
  super(...arguments);
1023
+ /**
1024
+ * Emits an event after the sign-out action is completed.
1025
+ *
1026
+ * @type {EventEmitter<void>}
1027
+ */
598
1028
  // eslint-disable-next-line @angular-eslint/no-output-rename
599
1029
  this.afterSignOut = new EventEmitter();
600
1030
  }
@@ -633,6 +1063,11 @@ class AuthComponent {
633
1063
  idClaims: this.authService.identityClaims$,
634
1064
  isAuthenticated: this.authService.isAuthenticated$,
635
1065
  });
1066
+ /**
1067
+ * Whether to hide the institution information.
1068
+ *
1069
+ * @type {InputSignal<boolean>}
1070
+ */
636
1071
  this.hideInstitution = input(false);
637
1072
  }
638
1073
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: AuthComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -659,8 +1094,18 @@ class AuthDirective {
659
1094
  this.authService = inject(AuthService);
660
1095
  this.ngIfDirective = inject(NgIf, { host: true });
661
1096
  this.takeUntilDestroyed = untilDestroyed();
1097
+ /**
1098
+ * The authorization handler or a string representing the handler.
1099
+ *
1100
+ * @type {AuthorizedHandler | null | string}
1101
+ */
662
1102
  this.authorizationHandler = null;
663
1103
  }
1104
+ /**
1105
+ * Sets the template to be displayed when the authorization check fails.
1106
+ *
1107
+ * @param {TemplateRef<NgIfContext<unknown>>} value - The template reference.
1108
+ */
664
1109
  // eslint-disable-next-line @angular-eslint/no-input-rename
665
1110
  set elseTemplate(value) {
666
1111
  this.ngIfDirective.ngIfElse = value;
@@ -689,6 +1134,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImpo
689
1134
  args: ['odxAuthElse']
690
1135
  }] } });
691
1136
 
1137
+ /**
1138
+ * Guard function to protect routes from unauthorized access.
1139
+ *
1140
+ * @param {AuthorizedHandler} [authorizedHandler] - Optional handler to check if the user is authorized.
1141
+ * @param {string | any[]} [redirectTo] - Optional URL or route to redirect unauthorized users to. Can be a string or an array of strings.
1142
+ * @param {boolean} [isExternal=false] - Optional flag to indicate if the redirection should be external. Defaults to false.
1143
+ * @returns {CanActivateFn} A function that implements the CanActivateFn interface.
1144
+ */
692
1145
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
693
1146
  function authGuard(authorizedHandler, redirectTo, isExternal = false) {
694
1147
  return (_, state) => {
@@ -733,6 +1186,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImpo
733
1186
  }]
734
1187
  }] });
735
1188
 
1189
+ /**
1190
+ * Guard function to prevent unauthorized access to routes.
1191
+ *
1192
+ * @param {AuthorizedHandler} authorizedHandler - Optional handler to check if the user is authorized.
1193
+ * @param {any[] | string} redirectTo - Optional URL or route to redirect unauthorized users to. Can be a string or an array of strings.
1194
+ * @param {boolean} isExternal - Optional flag to indicate if the redirection should be external. Defaults to false.
1195
+ * @returns {CanActivateFn} - A function that implements the CanActivateFn interface.
1196
+ */
736
1197
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
737
1198
  function unauthGuard(authorizedHandler, redirectTo, isExternal = false) {
738
1199
  return (_) => {