@ascentgl/ads-identity 22.0.0 → 22.2.0

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/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # ads-identity
2
-
3
- This library was generated with [Nx](https://nx.dev).
1
+ # ads-identity
2
+
3
+ This library was generated with [Nx](https://nx.dev).
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, computed, signal, inject, Injectable, Injector } from '@angular/core';
3
- import { of, throwError, catchError, finalize, shareReplay, map, switchMap } from 'rxjs';
2
+ import { InjectionToken, inject, signal, Service, computed, Injectable, Injector } from '@angular/core';
3
+ import { defer, share, of, switchMap, map, from, catchError, shareReplay, throwError, finalize } from 'rxjs';
4
4
  import { jwtDecode, InvalidTokenError } from 'jwt-decode';
5
5
  import { HttpErrorResponse, HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
6
6
  import { Router } from '@angular/router';
@@ -56,6 +56,9 @@ class TokenHelper {
56
56
  });
57
57
  return payload;
58
58
  }
59
+ static getHeader(token) {
60
+ return jwtDecode(token, { header: true });
61
+ }
59
62
  static isExpired(token, claim = 'exp', threshold = 0) {
60
63
  const throwInvalidColumnError = (column) => {
61
64
  throw new InvalidTokenError(`Column ${column} does not contain a valid number`);
@@ -93,6 +96,89 @@ class TokenHelper {
93
96
  }
94
97
 
95
98
  const IDENTITY_OPTIONS = new InjectionToken('IDENTITY_OPTIONS');
99
+ const RS256_ALGORITHM = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };
100
+
101
+ class JwksService {
102
+ constructor() {
103
+ this.httpClient = inject(HttpClient);
104
+ this.options = inject(IDENTITY_OPTIONS);
105
+ this.keys = signal(null, ...(ngDevMode ? [{ debugName: "keys" }] : []));
106
+ this.keysFetch = defer(() => this.fetchKeys(false)).pipe(share());
107
+ this.freshKeysFetch = defer(() => this.fetchKeys(true)).pipe(share());
108
+ }
109
+ verifyNewToken(token) {
110
+ return defer(() => {
111
+ if (TokenHelper.getHeader(token).alg !== 'RS256') {
112
+ return of(undefined);
113
+ }
114
+ return this.loadKeys().pipe(switchMap(() => this.assertTokenValid(token)));
115
+ });
116
+ }
117
+ verifyExistingToken(token) {
118
+ return defer(() => {
119
+ if (TokenHelper.getHeader(token).alg !== 'RS256') {
120
+ return of(true);
121
+ }
122
+ return (this.keys() ? of(true) : this.loadKeys()).pipe(switchMap(() => this.verifyToken(token)));
123
+ });
124
+ }
125
+ assertTokenValid(token) {
126
+ return this.verifyToken(token).pipe(map((valid) => {
127
+ if (!valid) {
128
+ throw new InvalidTokenError('Token signature verification failed against the IDMS JWKS');
129
+ }
130
+ }));
131
+ }
132
+ verifyToken(token) {
133
+ if (!this.keys()) {
134
+ return of(true);
135
+ }
136
+ const kid = TokenHelper.getHeader(token).kid;
137
+ const jwk = this.findKey(kid);
138
+ return jwk ? this.verifySignature(token, jwk) : this.retryWithFreshKeys(token, kid);
139
+ }
140
+ retryWithFreshKeys(token, kid) {
141
+ return this.loadKeys(true).pipe(switchMap((loaded) => {
142
+ if (!loaded) {
143
+ return of(true);
144
+ }
145
+ const rotatedKey = this.findKey(kid);
146
+ return rotatedKey ? this.verifySignature(token, rotatedKey) : of(false);
147
+ }));
148
+ }
149
+ verifySignature(token, jwk) {
150
+ const [header, payload, signature] = token.split('.');
151
+ return defer(() => from(crypto.subtle.importKey('jwk', jwk, RS256_ALGORITHM, false, ['verify']))).pipe(switchMap((key) => from(crypto.subtle.verify(RS256_ALGORITHM, key, JwksService.fromBase64Url(signature), new TextEncoder().encode(`${header}.${payload}`)))), catchError((error) => {
152
+ LogHelper.error('JWKS verification errored — rejecting token', error);
153
+ return of(false);
154
+ }));
155
+ }
156
+ loadKeys(fresh = false) {
157
+ return fresh ? this.freshKeysFetch : this.keysFetch;
158
+ }
159
+ fetchKeys(fresh) {
160
+ const url = `${this.options.backendUrl}/api/.well-known/jwks.json`;
161
+ return this.httpClient.get(fresh ? `${url}?fresh=${Date.now()}` : url).pipe(map((jwks) => {
162
+ this.keys.set(jwks.keys);
163
+ return true;
164
+ }), catchError((error) => {
165
+ LogHelper.error('JWKS endpoint unavailable — failing open', error);
166
+ return of(false);
167
+ }));
168
+ }
169
+ findKey(kid) {
170
+ return this.keys()?.find((key) => key.kid === kid) ?? null;
171
+ }
172
+ static fromBase64Url(value) {
173
+ const binary = atob(value.replace(/-/g, '+').replace(/_/g, '/'));
174
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
175
+ }
176
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: JwksService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
177
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.0.1", ngImport: i0, type: JwksService }); }
178
+ }
179
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: JwksService, decorators: [{
180
+ type: Service
181
+ }] });
96
182
 
97
183
  class TokenService {
98
184
  constructor() {
@@ -101,6 +187,9 @@ class TokenService {
101
187
  this.accessToken = signal(null, ...(ngDevMode ? [{ debugName: "accessToken" }] : []));
102
188
  this.refreshToken = signal(null, ...(ngDevMode ? [{ debugName: "refreshToken" }] : []));
103
189
  this.options = inject(IDENTITY_OPTIONS);
190
+ this.jwksService = inject(JwksService);
191
+ this.verifiedAccessToken = null;
192
+ this.pendingVerification = null;
104
193
  this.issuers = [];
105
194
  }
106
195
  setAccessToken(token) {
@@ -115,11 +204,22 @@ class TokenService {
115
204
  getRefreshToken() {
116
205
  return this.updateRefreshTokenSignal(this.getToken("IDMS_RefreshToken"));
117
206
  }
207
+ whenVerified() {
208
+ const pending = this.pendingVerification;
209
+ if (!pending) {
210
+ return of(undefined);
211
+ }
212
+ return pending.done.pipe(switchMap(() => (this.pendingVerification === pending ? of(undefined) : this.whenVerified())));
213
+ }
214
+ hasPendingVerification() {
215
+ return this.pendingVerification !== null;
216
+ }
118
217
  clearTokens() {
119
218
  localStorage.removeItem("IDMS_AccessToken");
120
219
  localStorage.removeItem("IDMS_RefreshToken");
121
220
  this.updateAccessTokenSignal(null);
122
221
  this.updateRefreshTokenSignal(null);
222
+ this.verifiedAccessToken = null;
123
223
  }
124
224
  updateAccessTokenSignal(newToken) {
125
225
  if (this.accessToken()?.token !== newToken?.token) {
@@ -140,6 +240,9 @@ class TokenService {
140
240
  const identityToken = this.wrapToken(token);
141
241
  if (identityToken) {
142
242
  localStorage.setItem(key, token);
243
+ if (key === "IDMS_AccessToken") {
244
+ this.verifiedAccessToken = token;
245
+ }
143
246
  }
144
247
  LogHelper.log(key, identityToken);
145
248
  return identityToken;
@@ -148,6 +251,10 @@ class TokenService {
148
251
  const actualToken = localStorage.getItem(key) ?? null;
149
252
  if (typeof actualToken === 'string') {
150
253
  try {
254
+ if (key === "IDMS_AccessToken" && !this.isVerified(actualToken)) {
255
+ this.verifyInBackground(actualToken);
256
+ return null;
257
+ }
151
258
  return this.wrapToken(actualToken);
152
259
  }
153
260
  catch (error) {
@@ -156,6 +263,39 @@ class TokenService {
156
263
  }
157
264
  return null;
158
265
  }
266
+ isVerified(token) {
267
+ if (TokenHelper.getHeader(token).alg !== 'RS256') {
268
+ return true;
269
+ }
270
+ return this.verifiedAccessToken === token;
271
+ }
272
+ verifyInBackground(token) {
273
+ if (this.pendingVerification?.token === token) {
274
+ return;
275
+ }
276
+ const done = this.jwksService.verifyExistingToken(token).pipe(catchError((error) => {
277
+ LogHelper.error(error);
278
+ return of(false);
279
+ }), map((valid) => {
280
+ if (this.pendingVerification?.token === token) {
281
+ this.pendingVerification = null;
282
+ }
283
+ return this.applyVerdict(token, valid);
284
+ }), shareReplay(1));
285
+ this.pendingVerification = { token, done };
286
+ done.subscribe();
287
+ }
288
+ applyVerdict(token, valid) {
289
+ if (localStorage.getItem("IDMS_AccessToken") !== token) {
290
+ return;
291
+ }
292
+ if (!valid) {
293
+ LogHelper.error(new InvalidTokenError('Stored access token failed JWKS signature verification'));
294
+ return this.clearTokens();
295
+ }
296
+ this.verifiedAccessToken = token;
297
+ this.getAccessToken();
298
+ }
159
299
  wrapToken(token) {
160
300
  const payload = TokenHelper.getPayload(token, {
161
301
  iss: this.getIssuers(),
@@ -185,12 +325,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
185
325
  args: [{ providedIn: 'root' }]
186
326
  }] });
187
327
 
328
+ class PkceHelper {
329
+ static createVerifier() {
330
+ const bytes = new Uint8Array(32);
331
+ crypto.getRandomValues(bytes);
332
+ return PkceHelper.toBase64Url(bytes);
333
+ }
334
+ static async createChallenge(verifier) {
335
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
336
+ return PkceHelper.toBase64Url(new Uint8Array(digest));
337
+ }
338
+ static async buildLoginRedirectUrl(options, redirectPath) {
339
+ const verifier = PkceHelper.createVerifier();
340
+ const challenge = await PkceHelper.createChallenge(verifier);
341
+ sessionStorage.setItem("IDMS_PkceVerifier", verifier);
342
+ return (`${options.frontendUrl}/apps/${options.application}` +
343
+ `?redirect=true&redirect_path=${encodeURIComponent(redirectPath)}` +
344
+ `&code_challenge=${challenge}&code_challenge_method=S256`);
345
+ }
346
+ static readVerifier() {
347
+ return sessionStorage.getItem("IDMS_PkceVerifier");
348
+ }
349
+ static clearVerifier() {
350
+ sessionStorage.removeItem("IDMS_PkceVerifier");
351
+ }
352
+ static toBase64Url(bytes) {
353
+ let binary = '';
354
+ for (const byte of bytes) {
355
+ binary += String.fromCharCode(byte);
356
+ }
357
+ return btoa(binary)
358
+ .replace(/\+/g, '-')
359
+ .replace(/\//g, '_')
360
+ .replace(/=+$/, '');
361
+ }
362
+ }
363
+
188
364
  class IdentityService {
189
365
  constructor() {
190
366
  this.accessToken = computed(() => this.tokenService.readonlyAccessToken(), ...(ngDevMode ? [{ debugName: "accessToken" }] : []));
191
367
  this.refreshToken = computed(() => this.tokenService.readonlyRefreshToken(), ...(ngDevMode ? [{ debugName: "refreshToken" }] : []));
192
368
  this.refreshInProgress = null;
193
369
  this.tokenService = inject(TokenService);
370
+ this.jwksService = inject(JwksService);
194
371
  this.httpClient = inject(HttpClient);
195
372
  this.options = inject(IDENTITY_OPTIONS);
196
373
  }
@@ -199,14 +376,25 @@ class IdentityService {
199
376
  }
200
377
  authenticate() {
201
378
  const accessToken = this.tokenService.getAccessToken();
202
- const refreshToken = this.tokenService.getRefreshToken();
203
379
  if (accessToken) {
204
380
  return of(accessToken);
205
381
  }
206
- if (refreshToken) {
207
- return this.refreshTokens();
208
- }
209
- return throwError(() => new InvalidTokenError('No valid tokens found'));
382
+ return this.decideWhenQuiescent();
383
+ }
384
+ decideWhenQuiescent() {
385
+ return this.tokenService.whenVerified().pipe(switchMap(() => {
386
+ const verifiedAccessToken = this.tokenService.getAccessToken();
387
+ if (verifiedAccessToken) {
388
+ return of(verifiedAccessToken);
389
+ }
390
+ if (this.tokenService.hasPendingVerification()) {
391
+ return this.decideWhenQuiescent();
392
+ }
393
+ if (this.tokenService.getRefreshToken()) {
394
+ return this.refreshTokens();
395
+ }
396
+ return throwError(() => new InvalidTokenError('No valid tokens found'));
397
+ }));
210
398
  }
211
399
  refreshTokens() {
212
400
  if (this.refreshInProgress)
@@ -222,6 +410,18 @@ class IdentityService {
222
410
  clearTokens() {
223
411
  this.tokenService.clearTokens();
224
412
  }
413
+ redeemCode(code) {
414
+ const codeVerifier = PkceHelper.readVerifier() ?? '';
415
+ PkceHelper.clearVerifier();
416
+ return this.httpClient
417
+ .post(`${this.options.backendUrl}/api/v${this.options.apiVersion ?? 1}/redeem-code`, {
418
+ code,
419
+ client_id: this.options.application,
420
+ redirect_uri: `${window.location.origin}${window.location.pathname}`,
421
+ code_verifier: codeVerifier,
422
+ })
423
+ .pipe(switchMap((response) => this.jwksService.verifyNewToken(response.access_token).pipe(map(() => response))));
424
+ }
225
425
  performRefresh() {
226
426
  const refreshToken = this.tokenService.getRefreshToken();
227
427
  if (!refreshToken) {
@@ -234,7 +434,7 @@ class IdentityService {
234
434
  .pipe(catchError((error) => {
235
435
  LogHelper.error(new InvalidTokenError(`Failed to obtain tokens. Reason: ${ErrorHelper.extractMessage(error).toLowerCase()}`));
236
436
  return throwError(() => error);
237
- }), map((response) => {
437
+ }), switchMap((response) => this.jwksService.verifyNewToken(response.access_token).pipe(map(() => response))), map((response) => {
238
438
  this.tokenService.setRefreshToken(response.refresh_token);
239
439
  return this.tokenService.setAccessToken(response.access_token);
240
440
  }));
@@ -251,34 +451,47 @@ const IDENTITY_AUTH_GUARD = (_route, state) => {
251
451
  const identityService = inject(IdentityService);
252
452
  const options = inject(IDENTITY_OPTIONS);
253
453
  return identityService.authenticate().pipe(catchError((error) => {
254
- window.location.href = `${options.frontendUrl}/apps/${options.application}?redirect=true&redirect_path=${encodeURIComponent(state.url)}`;
255
454
  LogHelper.error(error);
256
- return of(null);
455
+ return from(PkceHelper.buildLoginRedirectUrl(options, state.url)).pipe(map((url) => {
456
+ window.location.href = url;
457
+ return null;
458
+ }));
257
459
  }), map((accessToken) => !!accessToken));
258
460
  };
259
461
 
260
462
  const IDENTITY_AUTH_CALLBACK_GUARD = (route) => {
261
463
  const router = inject(Router);
262
464
  const tokenService = inject(TokenService);
263
- const throwAccessDenied = () => {
264
- throw new Error('Access denied');
265
- };
266
- try {
267
- if (!route.queryParams['access_token'] || !route.queryParams['refresh_token']) {
268
- throwAccessDenied();
269
- }
270
- tokenService.setAccessToken(route.queryParams['access_token']);
271
- tokenService.setRefreshToken(route.queryParams['refresh_token']);
272
- return router.navigateByUrl(route.queryParams['redirect_path'] || '/');
273
- }
274
- catch (error) {
465
+ const identityService = inject(IdentityService);
466
+ const jwksService = inject(JwksService);
467
+ const options = inject(IDENTITY_OPTIONS);
468
+ const denyWithError = (error) => {
275
469
  const routerOutlet = document.body.querySelector('router-outlet');
276
470
  if (routerOutlet) {
277
- routerOutlet.innerHTML = ErrorHelper.extractMessage(error);
471
+ routerOutlet.textContent = ErrorHelper.extractMessage(error);
278
472
  }
279
473
  LogHelper.error(error);
280
474
  return false;
475
+ };
476
+ const applyTokens = (accessToken, refreshToken) => {
477
+ tokenService.setAccessToken(accessToken);
478
+ tokenService.setRefreshToken(refreshToken);
479
+ return router.navigateByUrl(route.queryParams['redirect_path'] || '/');
480
+ };
481
+ const code = route.queryParams['code'];
482
+ if (code === 'none') {
483
+ return from(PkceHelper.buildLoginRedirectUrl(options, route.queryParams['redirect_path'] || '/')).pipe(map((url) => {
484
+ window.location.href = url;
485
+ return false;
486
+ }));
487
+ }
488
+ if (code) {
489
+ return identityService.redeemCode(code).pipe(switchMap(response => applyTokens(response.access_token, response.refresh_token)), catchError((error) => of(denyWithError(error))));
490
+ }
491
+ if (!route.queryParams['access_token'] || !route.queryParams['refresh_token']) {
492
+ return denyWithError('No access or refresh token provided');
281
493
  }
494
+ return jwksService.verifyNewToken(route.queryParams['access_token']).pipe(switchMap(() => applyTokens(route.queryParams['access_token'], route.queryParams['refresh_token'])), catchError((error) => of(denyWithError(error))));
282
495
  };
283
496
 
284
497
  class RequestHelper {
@@ -1 +1 @@
1
- {"version":3,"file":"ascentgl-ads-identity.mjs","sources":["../../../../libs/ads-identity/src/lib/helpers/log.helper.ts","../../../../libs/ads-identity/src/lib/helpers/error.helper.ts","../../../../libs/ads-identity/src/lib/helpers/token.helper.ts","../../../../libs/ads-identity/src/lib/types/internal-consts.ts","../../../../libs/ads-identity/src/lib/services/token.service.ts","../../../../libs/ads-identity/src/lib/services/identity.service.ts","../../../../libs/ads-identity/src/lib/guards/auth.guard.ts","../../../../libs/ads-identity/src/lib/guards/auth-callback.guard.ts","../../../../libs/ads-identity/src/lib/helpers/request.helper.ts","../../../../libs/ads-identity/src/lib/interceptors/request.interceptor.ts","../../../../libs/ads-identity/src/lib/interceptors/response.interceptor.ts","../../../../libs/ads-identity/src/lib/providers/identity.provider.ts"],"sourcesContent":["import { IdentityOptions } from '../types/internal-types';\n\nexport class LogHelper {\n static options?: IdentityOptions;\n\n private static readonly prefix = 'ADS-IDENTITY';\n\n static log(...args: unknown[]) {\n if (!this.options?.debugMode) return false;\n\n console.group(this.prefix, 'TOKEN UPDATE');\n\n console.log(...args);\n\n console.groupEnd();\n\n return true;\n }\n\n static error(...args: unknown[]) {\n if (!this.options?.debugMode) return false;\n\n console.group(this.prefix, 'EXCEPTION');\n\n console.error(...args);\n\n console.groupEnd();\n\n return true;\n }\n}\n","import { HttpErrorResponse } from '@angular/common/http';\nimport { GenericCollection } from '../types/internal-types';\n\nexport class ErrorHelper {\n static extractMessage(error: unknown): string {\n if (typeof error === 'string' && error) {\n return error;\n }\n\n let errorMessage: unknown;\n\n if (error instanceof HttpErrorResponse) {\n errorMessage = error.error?.message ?? error.message;\n }\n\n if (!errorMessage && typeof error === 'object' && typeof (<GenericCollection>error)['message'] === 'string') {\n errorMessage = (<GenericCollection>error)['message'];\n }\n\n return errorMessage && typeof errorMessage === 'string' ? errorMessage : 'Unknown server error';\n }\n}\n","import { InvalidTokenError, jwtDecode } from 'jwt-decode';\nimport { GenericCollection, IdentityJwtClaim, AccessTokenPayload, RefreshTokenPayload } from '../types/internal-types';\nimport { LogHelper } from './log.helper';\nimport { ErrorHelper } from './error.helper';\n\nexport class TokenHelper {\n static getPayload<T extends AccessTokenPayload | RefreshTokenPayload>(\n token: string,\n verifyClaims: GenericCollection = {},\n threshold = 0,\n ): T {\n const payload: T = jwtDecode(token);\n\n /**\n * throw error if token is expired\n */\n if (this.isExpired(payload, 'exp', threshold)) {\n throw new InvalidTokenError('Token expired');\n }\n\n /**\n * loop through claims to verify and make sure the payload match their values\n */\n Object.keys(verifyClaims).forEach((claim) => {\n const tokenClaimValue = payload[claim];\n const requestedClaimValue = verifyClaims[claim];\n\n if (!this.isClaimValid(tokenClaimValue, requestedClaimValue)) {\n throw new InvalidTokenError(\n `Invalid \"${claim}\" claim value \"${tokenClaimValue}\" provided in token. Required value is \"${requestedClaimValue}\"`,\n );\n }\n });\n\n return payload;\n }\n\n private static isExpired<T extends AccessTokenPayload | RefreshTokenPayload>(\n token: T,\n claim: IdentityJwtClaim = 'exp',\n threshold = 0,\n ): boolean {\n const throwInvalidColumnError = (column: string): true => {\n throw new InvalidTokenError(`Column ${column} does not contain a valid number`);\n };\n\n try {\n /**\n * get value from the column\n */\n let timestamp: unknown = token[claim];\n\n /**\n * if values is a number - compare it against current date\n */\n if (typeof timestamp === 'number') {\n /**\n * if threshold is set - extract it from the timestamp\n * NOTE: threshold allows to consider the timestamp expired earlier;\n * this allows to make sure the token does not expire while being transferred to backend\n */\n if (threshold > 0) {\n timestamp -= threshold;\n }\n\n return !!timestamp && timestamp * 1000 < new Date().getTime();\n }\n\n return throwInvalidColumnError(claim.toString());\n } catch (error: unknown) {\n LogHelper.error(new InvalidTokenError(ErrorHelper.extractMessage(error)));\n\n return true;\n }\n }\n\n private static isClaimValid(tokenClaim: unknown, valueToVerify: unknown) {\n /**\n * if any of the values is an array - apply special logic;\n * otherwise do strict comparison\n */\n if (tokenClaim instanceof Array || valueToVerify instanceof Array) {\n /**\n * if both claims are arrays - make sure they intersect\n */\n if (tokenClaim instanceof Array && valueToVerify instanceof Array) {\n return tokenClaim.filter((value) => valueToVerify.includes(value)).length > 0;\n }\n\n /**\n * if the first claim is an array and the second is a string -\n * check if the second is within the first\n */\n if (tokenClaim instanceof Array && typeof valueToVerify === 'string') {\n return tokenClaim.includes(valueToVerify);\n }\n\n /**\n * if the second claim is an array and the first is a string -\n * check if the first is within the second\n */\n if (valueToVerify instanceof Array && typeof tokenClaim === 'string') {\n return valueToVerify.includes(tokenClaim);\n }\n\n return false;\n }\n\n return tokenClaim === valueToVerify;\n }\n}\n","import { InjectionToken } from '@angular/core';\nimport { IdentityOptions } from './internal-types';\n\nexport const IDENTITY_OPTIONS = new InjectionToken<IdentityOptions>('IDENTITY_OPTIONS');\n","import { computed, inject, Injectable, signal } from '@angular/core';\nimport { IdentityToken, Nullable } from '../types/internal-types';\nimport { AccessToken, RefreshToken } from '../types/global-types';\nimport { TokenHelper } from '../helpers/token.helper';\nimport { InvalidTokenError } from 'jwt-decode';\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\nimport { TokenKey } from '../types/internal-enums';\nimport { LogHelper } from '../helpers/log.helper';\n\n@Injectable({ providedIn: 'root' })\nexport class TokenService {\n /**\n * NOTE: we expose access and refresh tokens as computed signals so that they can be\n * used in \"IdentityService\" to allow library users reactive readonly access to tokens,\n * since \"IdentityService\" is the only \"public\" service in the library\n */\n readonlyAccessToken = computed(() => this.accessToken());\n readonlyRefreshToken = computed(() => this.refreshToken());\n\n private readonly accessToken = signal<Nullable<AccessToken>>(null);\n private readonly refreshToken = signal<Nullable<RefreshToken>>(null);\n\n private readonly options = inject(IDENTITY_OPTIONS);\n\n private issuers: string[] = [];\n\n setAccessToken(token: unknown) {\n return this.updateAccessTokenSignal<AccessToken>(this.setToken<AccessToken>(TokenKey.accessToken, token));\n }\n\n getAccessToken() {\n return this.updateAccessTokenSignal(this.getToken(TokenKey.accessToken));\n }\n\n setRefreshToken(token: unknown) {\n return this.updateRefreshTokenSignal<RefreshToken>(this.setToken(TokenKey.refreshToken, token));\n }\n\n getRefreshToken() {\n return this.updateRefreshTokenSignal(this.getToken(TokenKey.refreshToken));\n }\n\n clearTokens() {\n localStorage.removeItem(TokenKey.accessToken);\n localStorage.removeItem(TokenKey.refreshToken);\n\n this.updateAccessTokenSignal(null);\n this.updateRefreshTokenSignal(null);\n }\n\n private updateAccessTokenSignal<T extends Nullable<AccessToken>>(newToken: Nullable<AccessToken>): T {\n if (this.accessToken()?.token !== newToken?.token) {\n this.accessToken.set(newToken);\n }\n\n return <T>this.accessToken();\n }\n\n private updateRefreshTokenSignal<T extends Nullable<RefreshToken>>(newToken: Nullable<RefreshToken>): T {\n if (this.refreshToken()?.token !== newToken?.token) {\n this.refreshToken.set(newToken);\n }\n\n return <T>this.refreshToken();\n }\n\n private setToken<T extends IdentityToken>(key: TokenKey, token: unknown): T {\n /**\n * make sure the provided token is at least a string\n */\n if (typeof token !== 'string') {\n throw new InvalidTokenError(`Provided ${key} value is not a string`);\n }\n\n /**\n * get identity token\n */\n const identityToken = this.wrapToken<T>(token);\n\n /**\n * if provided - save it to local storage\n */\n if (identityToken) {\n localStorage.setItem(key, token);\n }\n\n LogHelper.log(key, identityToken);\n\n return identityToken;\n }\n\n private getToken<T extends IdentityToken>(key: TokenKey): Nullable<T> {\n /**\n * get token value from local storage or use null\n */\n const actualToken = localStorage.getItem(key) ?? null;\n\n if (typeof actualToken === 'string') {\n try {\n return this.wrapToken<T>(actualToken);\n } catch (error) {\n LogHelper.error(error);\n }\n }\n\n return null;\n }\n\n private wrapToken<T extends IdentityToken>(token: string): T {\n /**\n * parse a token and get its payload\n * NOTE: errors will be thrown if the token is expired or has invalid claims\n */\n const payload = TokenHelper.getPayload(\n token,\n {\n iss: this.getIssuers(),\n sub: this.options.application,\n },\n 60,\n );\n\n /**\n * return identity token object\n */\n return <T>{ token, payload: payload };\n }\n\n private getIssuers(): string[] {\n /**\n * if we already have issuers - ise them\n */\n if (this.issuers.length) {\n return this.issuers;\n }\n\n /**\n * get issuer value from the options\n */\n const issuer = this.options.serviceIssuer ?? this.options.backendUrl;\n\n this.issuers = [issuer];\n\n /**\n * make sure we have both variants (with and without \"/api\" suffix) as issuers\n * todo \"/api\" suffix verification must be removed when \"/api\" suffix will be removed from the \"issuer\" in IDMS\n */\n if (issuer.endsWith('/api')) {\n this.issuers.push(issuer.substring(0, issuer.lastIndexOf('/api')));\n } else {\n this.issuers.push(`${issuer}/api`);\n }\n\n return this.issuers;\n }\n}\n","import { computed, inject, Injectable } from '@angular/core';\nimport { catchError, finalize, map, Observable, of, shareReplay, throwError } from 'rxjs';\nimport { TokenService } from './token.service';\nimport { HttpClient } from '@angular/common/http';\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\nimport { Nullable, RefreshTokenResponse } from '../types/internal-types';\nimport { LogHelper } from '../helpers/log.helper';\nimport { AccessToken } from '../types/global-types';\nimport { InvalidTokenError } from 'jwt-decode';\nimport { ErrorHelper } from '../helpers/error.helper';\nimport { IdentityServiceInterface } from '../interfaces/identity-service.interface';\n\n@Injectable({ providedIn: 'root' })\nexport class IdentityService implements IdentityServiceInterface {\n /**\n * NOTE: we expose access and refresh tokens as computed signals to allow\n * library users reactive readonly access to tokens, since \"IdentityService\"\n * is the only \"public\" service in the library\n */\n readonly accessToken = computed(() => this.tokenService.readonlyAccessToken());\n readonly refreshToken = computed(() => this.tokenService.readonlyRefreshToken());\n\n private refreshInProgress: Nullable<Observable<AccessToken>> = null;\n\n private readonly tokenService = inject(TokenService);\n private readonly httpClient = inject(HttpClient);\n private readonly options = inject(IDENTITY_OPTIONS);\n\n isAuthenticated() {\n return !!this.tokenService.getAccessToken();\n }\n\n authenticate(): Observable<AccessToken> {\n /**\n * get access and refresh token\n */\n const accessToken = this.tokenService.getAccessToken();\n const refreshToken = this.tokenService.getRefreshToken();\n\n /**\n * if it is available - return it; otherwise try refreshing the tokens by calling IDMS\n */\n if (accessToken) {\n return of(accessToken);\n }\n\n /**\n * if a refresh token is available - try getting new tokens\n */\n if (refreshToken) {\n return this.refreshTokens();\n }\n\n /**\n * throw error if no tokens are available\n */\n return throwError(() => new InvalidTokenError('No valid tokens found'));\n }\n\n refreshTokens(): Observable<AccessToken> {\n if (this.refreshInProgress) return this.refreshInProgress;\n\n this.refreshInProgress = this.performRefresh().pipe(\n catchError((error: unknown) => {\n this.refreshInProgress = null;\n return throwError(() => error);\n }),\n finalize(() => {\n this.refreshInProgress = null;\n }),\n shareReplay(1),\n );\n\n return this.refreshInProgress;\n }\n\n clearTokens() {\n this.tokenService.clearTokens();\n }\n\n private performRefresh() {\n const refreshToken = this.tokenService.getRefreshToken();\n\n /**\n * throw error if the refresh token is not available\n */\n if (!refreshToken) {\n return throwError(() => new InvalidTokenError('No refresh token found'));\n }\n\n /**\n * make a request to Identity service to get new tokens\n */\n return this.httpClient\n .get<RefreshTokenResponse>(`${this.options.backendUrl}/api/v${this.options.apiVersion ?? 1}/refresh-token`, {\n headers: { Authorization: `Bearer ${refreshToken.token}` },\n })\n .pipe(\n catchError((error: unknown) => {\n /**\n * log error if it happens\n */\n LogHelper.error(\n new InvalidTokenError(\n `Failed to obtain tokens. Reason: ${ErrorHelper.extractMessage(error).toLowerCase()}`,\n ),\n );\n\n return throwError(() => error);\n }),\n map((response) => {\n /**\n * set refresh token\n */\n this.tokenService.setRefreshToken(response.refresh_token);\n\n /**\n * set and return access token\n */\n return this.tokenService.setAccessToken(response.access_token);\n }),\n );\n }\n}\n","import { CanActivateFn } from '@angular/router';\nimport { inject } from '@angular/core';\nimport { IdentityService } from '../services/identity.service';\nimport { catchError, map, of } from 'rxjs';\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\nimport { LogHelper } from '../helpers/log.helper';\n\nexport const IDENTITY_AUTH_GUARD: CanActivateFn = (_route, state) => {\n const identityService = inject(IdentityService);\n const options = inject(IDENTITY_OPTIONS);\n\n /**\n * check if the user is authenticated;\n * if an error happened - redirect to IDMS url to re-login;\n * return true on success and false on error\n */\n return identityService.authenticate().pipe(\n catchError((error) => {\n window.location.href = `${options.frontendUrl}/apps/${options.application}?redirect=true&redirect_path=${encodeURIComponent(\n state.url,\n )}`;\n\n LogHelper.error(error);\n\n return of(null);\n }),\n map((accessToken) => !!accessToken),\n );\n};\n","import { CanActivateFn, Router } from '@angular/router';\nimport { inject } from '@angular/core';\nimport { TokenService } from '../services/token.service';\nimport { LogHelper } from '../helpers/log.helper';\nimport { ErrorHelper } from '../helpers/error.helper';\n\nexport const IDENTITY_AUTH_CALLBACK_GUARD: CanActivateFn = (route) => {\n const router = inject(Router);\n const tokenService = inject(TokenService);\n\n const throwAccessDenied = () => {\n throw new Error('Access denied');\n };\n\n /**\n * save access and refresh tokens and redirect either to root or to provided \"redirect_path\";\n * if an error happened - display an error message on the page and return false to stop further navigation\n */\n try {\n if (!route.queryParams['access_token'] || !route.queryParams['refresh_token']) {\n throwAccessDenied();\n }\n\n tokenService.setAccessToken(route.queryParams['access_token']);\n\n tokenService.setRefreshToken(route.queryParams['refresh_token']);\n\n return router.navigateByUrl(route.queryParams['redirect_path'] || '/');\n } catch (error: unknown) {\n const routerOutlet = document.body.querySelector('router-outlet');\n\n if (routerOutlet) {\n routerOutlet.innerHTML = ErrorHelper.extractMessage(error);\n }\n\n LogHelper.error(error);\n\n return false;\n }\n};\n","import { HttpErrorResponse, HttpRequest } from '@angular/common/http';\nimport { throwError } from 'rxjs';\nimport { Injector } from '@angular/core';\nimport { IdentityOptions } from '../types/internal-types';\nimport { AccessToken } from '../types/global-types';\n\nexport class RequestHelper {\n static options?: IdentityOptions;\n\n static isIdentityEndpointRequest(request: HttpRequest<unknown>): boolean {\n return (\n (!!this.options?.backendUrl && request.url.startsWith(this.options?.backendUrl)) ||\n (!!this.options?.frontendUrl && request.url.startsWith(this.options?.frontendUrl))\n );\n }\n\n static isAuthorizedRequest(request: HttpRequest<unknown>): boolean {\n return !this.options?.requestFilter || this.options.requestFilter(request);\n }\n\n static isAuthorizationError(error: unknown): boolean {\n return error instanceof HttpErrorResponse && error.status === 401;\n }\n\n static attachAuthorizationHeader(request: HttpRequest<unknown>, accessToken: AccessToken): HttpRequest<unknown> {\n return request.clone({\n setHeaders: { authorization: `Bearer ${accessToken.token}`, 'auth-source': 'IDMS' },\n });\n }\n\n static onAuthorizationError(error: unknown, injector: Injector) {\n if (this.options?.onAuthorizationError) {\n this.options?.onAuthorizationError(error, injector);\n }\n\n return throwError(() => error);\n }\n}\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { catchError, Observable, switchMap } from 'rxjs';\nimport { inject, Injector } from '@angular/core';\nimport { IdentityService } from '../services/identity.service';\nimport { RequestHelper } from '../helpers/request.helper';\n\nexport function requestInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {\n const identityService = inject(IdentityService);\n const injector = inject(Injector);\n\n /**\n * if it is a request to Identity Service or not an authorized request - do not modify it\n */\n if (RequestHelper.isIdentityEndpointRequest(request) || !RequestHelper.isAuthorizedRequest(request)) {\n return next(request);\n }\n\n /**\n * if the current user is authenticated - attach the \"Authorization\" header to the request;\n * or process the error if it happens\n */\n return identityService.authenticate().pipe(\n catchError((error: unknown) => RequestHelper.onAuthorizationError(error, injector)),\n switchMap((accessToken) => next(RequestHelper.attachAuthorizationHeader(request, accessToken))),\n );\n}\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { catchError, Observable, switchMap, throwError } from 'rxjs';\nimport { inject, Injector } from '@angular/core';\nimport { IdentityService } from '../services/identity.service';\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\nimport { RequestHelper } from '../helpers/request.helper';\n\nexport function responseInterceptor(\n request: HttpRequest<unknown>,\n next: HttpHandlerFn,\n): Observable<HttpEvent<unknown>> {\n const options = inject(IDENTITY_OPTIONS);\n const identityService = inject(IdentityService);\n const injector = inject(Injector);\n\n /**\n * process all response errors\n */\n return next(request).pipe(\n catchError((error: unknown) => {\n /**\n * if error happened with Identity Service request, or not-authorized request, or it is not an authorization (401) error -\n * do not change anything and return the error as is\n */\n if (\n RequestHelper.isIdentityEndpointRequest(request) ||\n !RequestHelper.isAuthorizedRequest(request) ||\n !RequestHelper.isAuthorizationError(error)\n ) {\n return throwError(() => error);\n }\n\n /**\n * if options do not allow trying and refresh tokens on error 401 -\n * process the error and return it\n */\n if (!options.refreshTokenOnAuthError) {\n return RequestHelper.onAuthorizationError(error, injector);\n }\n\n /**\n * if tokens were refreshed - attach the \"Authorization\" header to the request and call it again;\n * or process the error if it happens\n */\n return identityService.refreshTokens().pipe(\n switchMap((accessToken) => next(RequestHelper.attachAuthorizationHeader(request, accessToken))),\n catchError((error: unknown) => RequestHelper.onAuthorizationError(error, injector)),\n );\n }),\n );\n}\n","import { provideHttpClient, withInterceptors } from '@angular/common/http';\nimport { requestInterceptor } from '../interceptors/request.interceptor';\nimport { responseInterceptor } from '../interceptors/response.interceptor';\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\nimport { LogHelper } from '../helpers/log.helper';\nimport { RequestHelper } from '../helpers/request.helper';\nimport { IdentityOptions } from '../types/internal-types';\n\nexport function provideIdentity(options: IdentityOptions) {\n LogHelper.options = options;\n RequestHelper.options = options;\n\n return [\n { provide: IDENTITY_OPTIONS, useValue: options },\n provideHttpClient(withInterceptors([requestInterceptor, responseInterceptor])),\n ];\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ascentgl-ads-identity.mjs","sources":["../../../../libs/ads-identity/src/lib/helpers/log.helper.ts","../../../../libs/ads-identity/src/lib/helpers/error.helper.ts","../../../../libs/ads-identity/src/lib/helpers/token.helper.ts","../../../../libs/ads-identity/src/lib/types/internal-consts.ts","../../../../libs/ads-identity/src/lib/services/jwks.service.ts","../../../../libs/ads-identity/src/lib/services/token.service.ts","../../../../libs/ads-identity/src/lib/helpers/pkce.helper.ts","../../../../libs/ads-identity/src/lib/services/identity.service.ts","../../../../libs/ads-identity/src/lib/guards/auth.guard.ts","../../../../libs/ads-identity/src/lib/guards/auth-callback.guard.ts","../../../../libs/ads-identity/src/lib/helpers/request.helper.ts","../../../../libs/ads-identity/src/lib/interceptors/request.interceptor.ts","../../../../libs/ads-identity/src/lib/interceptors/response.interceptor.ts","../../../../libs/ads-identity/src/lib/providers/identity.provider.ts"],"sourcesContent":["import { IdentityOptions } from '../types/internal-types';\r\n\r\nexport class LogHelper {\r\n static options?: IdentityOptions;\r\n\r\n private static readonly prefix = 'ADS-IDENTITY';\r\n\r\n static log(...args: unknown[]) {\r\n if (!this.options?.debugMode) return false;\r\n\r\n console.group(this.prefix, 'TOKEN UPDATE');\r\n\r\n console.log(...args);\r\n\r\n console.groupEnd();\r\n\r\n return true;\r\n }\r\n\r\n static error(...args: unknown[]) {\r\n if (!this.options?.debugMode) return false;\r\n\r\n console.group(this.prefix, 'EXCEPTION');\r\n\r\n console.error(...args);\r\n\r\n console.groupEnd();\r\n\r\n return true;\r\n }\r\n}\r\n","import { HttpErrorResponse } from '@angular/common/http';\r\nimport { GenericCollection } from '../types/internal-types';\r\n\r\nexport class ErrorHelper {\r\n static extractMessage(error: unknown): string {\r\n if (typeof error === 'string' && error) {\r\n return error;\r\n }\r\n\r\n let errorMessage: unknown;\r\n\r\n if (error instanceof HttpErrorResponse) {\r\n errorMessage = error.error?.message ?? error.message;\r\n }\r\n\r\n if (!errorMessage && typeof error === 'object' && typeof (<GenericCollection>error)['message'] === 'string') {\r\n errorMessage = (<GenericCollection>error)['message'];\r\n }\r\n\r\n return errorMessage && typeof errorMessage === 'string' ? errorMessage : 'Unknown server error';\r\n }\r\n}\r\n","import { InvalidTokenError, jwtDecode, JwtHeader } from 'jwt-decode';\r\nimport { GenericCollection, IdentityJwtClaim, AccessTokenPayload, RefreshTokenPayload } from '../types/internal-types';\r\nimport { LogHelper } from './log.helper';\r\nimport { ErrorHelper } from './error.helper';\r\n\r\nexport class TokenHelper {\r\n static getPayload<T extends AccessTokenPayload | RefreshTokenPayload>(\r\n token: string,\r\n verifyClaims: GenericCollection = {},\r\n threshold = 0,\r\n ): T {\r\n const payload: T = jwtDecode(token);\r\n\r\n /**\r\n * throw error if the token is expired\r\n */\r\n if (this.isExpired(payload, 'exp', threshold)) {\r\n throw new InvalidTokenError('Token expired');\r\n }\r\n\r\n /**\r\n * loop through claims to verify and make sure the payload matches their values\r\n */\r\n Object.keys(verifyClaims).forEach((claim) => {\r\n const tokenClaimValue = payload[claim];\r\n const requestedClaimValue = verifyClaims[claim];\r\n\r\n if (!this.isClaimValid(tokenClaimValue, requestedClaimValue)) {\r\n throw new InvalidTokenError(\r\n `Invalid \"${claim}\" claim value \"${tokenClaimValue}\" provided in token. Required value is \"${requestedClaimValue}\"`,\r\n );\r\n }\r\n });\r\n\r\n return payload;\r\n }\r\n\r\n /**\r\n * decoded JOSE header (alg/kid) — used to detect asymmetrically signed\r\n * (RS256) tokens that must be verified against the IDMS JWKS. Throws\r\n * InvalidTokenError on malformed input, like every other decoding here.\r\n */\r\n static getHeader(token: string): JwtHeader {\r\n return jwtDecode<JwtHeader>(token, { header: true });\r\n }\r\n\r\n private static isExpired<T extends AccessTokenPayload | RefreshTokenPayload>(\r\n token: T,\r\n claim: IdentityJwtClaim = 'exp',\r\n threshold = 0,\r\n ): boolean {\r\n const throwInvalidColumnError = (column: string): true => {\r\n throw new InvalidTokenError(`Column ${column} does not contain a valid number`);\r\n };\r\n\r\n try {\r\n /**\r\n * get value from the column\r\n */\r\n let timestamp: unknown = token[claim];\r\n\r\n /**\r\n * if values is a number - compare it against the current date\r\n */\r\n if (typeof timestamp === 'number') {\r\n /**\r\n * if a threshold is set - extract it from the timestamp\r\n * NOTE: a threshold allows considering the timestamp expired earlier;\r\n * this allows to make sure the token does not expire while being transferred to backend\r\n */\r\n if (threshold > 0) {\r\n timestamp -= threshold;\r\n }\r\n\r\n return !!timestamp && timestamp * 1000 < new Date().getTime();\r\n }\r\n\r\n return throwInvalidColumnError(claim.toString());\r\n } catch (error: unknown) {\r\n LogHelper.error(new InvalidTokenError(ErrorHelper.extractMessage(error)));\r\n\r\n return true;\r\n }\r\n }\r\n\r\n private static isClaimValid(tokenClaim: unknown, valueToVerify: unknown) {\r\n /**\r\n * if any of the values is an array - apply special logic;\r\n * otherwise do strict comparison\r\n */\r\n if (tokenClaim instanceof Array || valueToVerify instanceof Array) {\r\n /**\r\n * if both claims are arrays - make sure they intersect\r\n */\r\n if (tokenClaim instanceof Array && valueToVerify instanceof Array) {\r\n return tokenClaim.filter((value) => valueToVerify.includes(value)).length > 0;\r\n }\r\n\r\n /**\r\n * if the first claim is an array and the second is a string -\r\n * check if the second is within the first\r\n */\r\n if (tokenClaim instanceof Array && typeof valueToVerify === 'string') {\r\n return tokenClaim.includes(valueToVerify);\r\n }\r\n\r\n /**\r\n * if the second claim is an array and the first is a string -\r\n * check if the first is within the second\r\n */\r\n if (valueToVerify instanceof Array && typeof tokenClaim === 'string') {\r\n return valueToVerify.includes(tokenClaim);\r\n }\r\n\r\n return false;\r\n }\r\n\r\n return tokenClaim === valueToVerify;\r\n }\r\n}\r\n","import { InjectionToken } from '@angular/core';\r\nimport { IdentityOptions } from './internal-types';\r\n\r\nexport const IDENTITY_OPTIONS = new InjectionToken<IdentityOptions>('IDENTITY_OPTIONS');\r\n\r\n/**\r\n * RSASSA-PKCS1-v1_5 with SHA-256 — the one algorithm IDMS signs RS256 tokens with\r\n */\r\nexport const RS256_ALGORITHM = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };\r\n","import { inject, Service, signal } from '@angular/core';\r\nimport { HttpClient } from '@angular/common/http';\r\nimport { catchError, defer, from, map, Observable, of, share, switchMap } from 'rxjs';\r\nimport { InvalidTokenError } from 'jwt-decode';\r\nimport { IDENTITY_OPTIONS, RS256_ALGORITHM } from '../types/internal-consts';\r\nimport { Jwks, Nullable } from '../types/internal-types';\r\nimport { TokenHelper } from '../helpers/token.helper';\r\nimport { LogHelper } from '../helpers/log.helper';\r\n\r\n/**\r\n * Verifies RS256-signed IDMS ACCESS tokens against the public keys published\r\n * at {backendUrl}/api/.well-known/jwks.json (Web Crypto, no dependencies).\r\n *\r\n * ARCHITECTURE INVARIANT: only the ACCESS token can be RS256-signed. Refresh\r\n * tokens are ALWAYS HS256 by IDMS design and are never routed through this\r\n * service — their handling is byte-for-byte the pre-JWKS legacy behavior.\r\n *\r\n * The key cache lives in memory only — it dies with the page (by design) and\r\n * is eagerly replaced on every token acquisition (\"until a new access key is\r\n * obtained\"). HS256 tokens cannot be verified client-side and bypass\r\n * everything here.\r\n */\r\n@Service()\r\nexport class JwksService {\r\n private readonly httpClient = inject(HttpClient);\r\n private readonly options = inject(IDENTITY_OPTIONS);\r\n\r\n /** in-memory key cache — signal state, replaced wholesale on every fetch */\r\n private readonly keys = signal<Nullable<Jwks['keys']>>(null);\r\n\r\n /**\r\n * concurrent subscribers join ONE in-flight request (`share`), and the\r\n * connection resets on completion — so the next acquisition fetches anew.\r\n * NOT shareReplay: replaying a completed fetch's body is exactly the stale\r\n * key hazard. The cache-busted channel is a separate stream — a kid-rotation\r\n * retry must never piggyback on an in-flight plain fetch (its body may be\r\n * stale and would false-reject a legitimately rotated key).\r\n */\r\n private readonly keysFetch = defer(() => this.fetchKeys(false)).pipe(share());\r\n private readonly freshKeysFetch = defer(() => this.fetchKeys(true)).pipe(share());\r\n\r\n /**\r\n * acquisition-time check (redeem-code / legacy callback / refresh) for the\r\n * ACCESS token: eagerly refresh the JWKS, then verify the token BEFORE it\r\n * is stored. Errors with InvalidTokenError on a bad signature or unknown\r\n * kid.\r\n *\r\n * Emits SYNCHRONOUSLY for an HS256 token — legacy flows must keep their\r\n * exact timing (and fire no JWKS request at all). Resolves silently when\r\n * the JWKS endpoint is unavailable (unavailability lets the token pass;\r\n * an IDMS blip must not lock users out — mismatches still reject whenever\r\n * keys ARE available).\r\n */\r\n verifyNewToken(token: string): Observable<void> {\r\n /**\r\n * defer so malformed input (getHeader throws) surfaces as an Observable\r\n * error instead of a synchronous throw out of the call site — while the\r\n * HS256 fast path still emits synchronously on subscription\r\n */\r\n return defer(() => {\r\n if (TokenHelper.getHeader(token).alg !== 'RS256') {\r\n return of(undefined);\r\n }\r\n\r\n return this.loadKeys().pipe(switchMap(() => this.assertTokenValid(token)));\r\n });\r\n }\r\n\r\n /**\r\n * read-path check for a localStorage value that is not the current page's\r\n * verified copy (cold reload / cross-tab rotation / tampering). Lazily\r\n * fetches the JWKS when the cache is empty. Emits false ONLY on a definite\r\n * mismatch; unavailability lets the token pass (never locks users out).\r\n */\r\n verifyExistingToken(token: string): Observable<boolean> {\r\n return defer(() => {\r\n if (TokenHelper.getHeader(token).alg !== 'RS256') {\r\n return of(true);\r\n }\r\n\r\n return (this.keys() ? of(true) : this.loadKeys()).pipe(switchMap(() => this.verifyToken(token)));\r\n });\r\n }\r\n\r\n /**\r\n * acquisition gate: turns a failed check into a stream error, so the caller\r\n * can never store a token by forgetting to inspect a boolean\r\n */\r\n private assertTokenValid(token: string): Observable<void> {\r\n return this.verifyToken(token).pipe(\r\n map((valid) => {\r\n if (!valid) {\r\n throw new InvalidTokenError('Token signature verification failed against the IDMS JWKS');\r\n }\r\n }),\r\n );\r\n }\r\n\r\n private verifyToken(token: string): Observable<boolean> {\r\n /**\r\n * keys unavailable (endpoint unreachable and nothing cached) — let the\r\n * token pass rather than lock users out\r\n */\r\n if (!this.keys()) {\r\n return of(true);\r\n }\r\n\r\n const kid = TokenHelper.getHeader(token).kid;\r\n const jwk = this.findKey(kid);\r\n\r\n return jwk ? this.verifySignature(token, jwk) : this.retryWithFreshKeys(token, kid);\r\n }\r\n\r\n /**\r\n * unknown kid against a possibly stale cache: the signing key may have\r\n * rotated since we fetched — force ONE fresh (cache-busted) fetch before\r\n * rejecting. A failed re-fetch cannot distinguish rotation from outage,\r\n * so it also lets the token pass.\r\n */\r\n private retryWithFreshKeys(token: string, kid?: string): Observable<boolean> {\r\n return this.loadKeys(true).pipe(\r\n switchMap((loaded) => {\r\n if (!loaded) {\r\n return of(true);\r\n }\r\n\r\n const rotatedKey = this.findKey(kid);\r\n\r\n return rotatedKey ? this.verifySignature(token, rotatedKey) : of(false);\r\n }),\r\n );\r\n }\r\n\r\n private verifySignature(token: string, jwk: JsonWebKey): Observable<boolean> {\r\n const [header, payload, signature] = token.split('.');\r\n\r\n return defer(() => from(crypto.subtle.importKey('jwk', jwk, RS256_ALGORITHM, false, ['verify']))).pipe(\r\n switchMap((key) =>\r\n from(\r\n crypto.subtle.verify(\r\n RS256_ALGORITHM,\r\n key,\r\n JwksService.fromBase64Url(signature),\r\n new TextEncoder().encode(`${header}.${payload}`),\r\n ),\r\n ),\r\n ),\r\n catchError((error: unknown) => {\r\n LogHelper.error('JWKS verification errored — rejecting token', error);\r\n\r\n return of(false);\r\n }),\r\n );\r\n }\r\n\r\n private loadKeys(fresh = false): Observable<boolean> {\r\n return fresh ? this.freshKeysFetch : this.keysFetch;\r\n }\r\n\r\n private fetchKeys(fresh: boolean): Observable<boolean> {\r\n const url = `${this.options.backendUrl}/api/.well-known/jwks.json`;\r\n\r\n /**\r\n * `fresh` busts the browser HTTP cache (the endpoint serves max-age=3600)\r\n * for the kid-rotation retry — a plain refetching would just replay the\r\n * cached body\r\n */\r\n return this.httpClient.get<Jwks>(fresh ? `${url}?fresh=${Date.now()}` : url).pipe(\r\n map((jwks) => {\r\n this.keys.set(jwks.keys);\r\n\r\n return true;\r\n }),\r\n catchError((error: unknown) => {\r\n LogHelper.error('JWKS endpoint unavailable — failing open', error);\r\n\r\n return of(false);\r\n }),\r\n );\r\n }\r\n\r\n private findKey(kid?: string): Nullable<JsonWebKey> {\r\n return this.keys()?.find((key) => key.kid === kid) ?? null;\r\n }\r\n\r\n /**\r\n * returns Uint8Array<ArrayBuffer> (not the bare-Uint8Array default of\r\n * ArrayBufferLike) — Web Crypto's BufferSource rejects views that could be\r\n * backed by a SharedArrayBuffer\r\n */\r\n private static fromBase64Url(value: string): Uint8Array<ArrayBuffer> {\r\n const binary = atob(value.replace(/-/g, '+').replace(/_/g, '/'));\r\n\r\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\r\n }\r\n}\r\n","import { computed, inject, Injectable, signal } from '@angular/core';\r\nimport { catchError, map, Observable, of, shareReplay, switchMap } from 'rxjs';\r\nimport { IdentityToken, Nullable } from '../types/internal-types';\r\nimport { AccessToken, RefreshToken } from '../types/global-types';\r\nimport { TokenHelper } from '../helpers/token.helper';\r\nimport { InvalidTokenError } from 'jwt-decode';\r\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\r\nimport { TokenKey } from '../types/internal-enums';\r\nimport { LogHelper } from '../helpers/log.helper';\r\nimport { JwksService } from './jwks.service';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class TokenService {\r\n /**\r\n * NOTE: we expose access and refresh tokens as computed signals so that they can be\r\n * used in \"IdentityService\" to allow library users reactive readonly access to tokens,\r\n * since \"IdentityService\" is the only \"public\" service in the library\r\n */\r\n readonlyAccessToken = computed(() => this.accessToken());\r\n readonlyRefreshToken = computed(() => this.refreshToken());\r\n\r\n private readonly accessToken = signal<Nullable<AccessToken>>(null);\r\n private readonly refreshToken = signal<Nullable<RefreshToken>>(null);\r\n\r\n private readonly options = inject(IDENTITY_OPTIONS);\r\n private readonly jwksService = inject(JwksService);\r\n\r\n /**\r\n * FAIL-CLOSED read gate for the ACCESS token: the token string this page\r\n * has verified (or adopted). A localStorage value that differs is NEVER\r\n * exposed until a background JWKS check adopts it (cold reload / cross-tab\r\n * rotation) or clears everything (tampering). HS256 access tokens bypass\r\n * the gate — they cannot be verified client-side by design.\r\n *\r\n * ARCHITECTURE INVARIANT: refresh tokens are ALWAYS HS256 by IDMS design,\r\n * so only the access token is ever gated/verified; the refresh path is\r\n * byte-for-byte the pre-JWKS legacy behavior.\r\n */\r\n private verifiedAccessToken: Nullable<string> = null;\r\n\r\n /** the single in-flight background verification (access token only) */\r\n private pendingVerification: Nullable<{ token: string; done: Observable<void> }> = null;\r\n\r\n private issuers: string[] = [];\r\n\r\n setAccessToken(token: unknown) {\r\n return this.updateAccessTokenSignal<AccessToken>(this.setToken<AccessToken>(TokenKey.accessToken, token));\r\n }\r\n\r\n getAccessToken() {\r\n return this.updateAccessTokenSignal(this.getToken(TokenKey.accessToken));\r\n }\r\n\r\n setRefreshToken(token: unknown) {\r\n return this.updateRefreshTokenSignal<RefreshToken>(this.setToken(TokenKey.refreshToken, token));\r\n }\r\n\r\n getRefreshToken() {\r\n return this.updateRefreshTokenSignal(this.getToken(TokenKey.refreshToken));\r\n }\r\n\r\n /**\r\n * emits+completes once background verification is quiescent; never errors.\r\n * IdentityService.authenticate() composes this so guarded navigation and\r\n * Bearer attachment wait for the verdict instead of failing on the\r\n * fail-closed null read. Emits synchronously when nothing is pending.\r\n *\r\n * Re-checks after each settled verdict: a NEWER token may have superseded\r\n * the in-flight check while we waited (cross-tab rotation mid-fetch) —\r\n * resolving on the stale verdict alone would send authenticate() into a\r\n * needless token rotation.\r\n */\r\n whenVerified(): Observable<void> {\r\n const pending = this.pendingVerification;\r\n\r\n if (!pending) {\r\n return of(undefined);\r\n }\r\n\r\n return pending.done.pipe(\r\n /**\r\n * recurse ONLY when a different check replaced the slot — the settled\r\n * verdict may replay before our own finalizing clears it, and recursing\r\n * into the same entry would loop forever\r\n */\r\n switchMap(() => (this.pendingVerification === pending ? of(undefined) : this.whenVerified())),\r\n );\r\n }\r\n\r\n /**\r\n * whether background access-token verification is in flight RIGHT NOW.\r\n * whenVerified() only covers checks that existed while it was waiting — a\r\n * read performed AFTER it settles may itself discover a newer unverified\r\n * token and start a fresh check; authenticate() uses this to wait for that\r\n * verdict too instead of treating the fail-closed null read as \"no token\".\r\n */\r\n hasPendingVerification(): boolean {\r\n return this.pendingVerification !== null;\r\n }\r\n\r\n clearTokens() {\r\n localStorage.removeItem(TokenKey.accessToken);\r\n localStorage.removeItem(TokenKey.refreshToken);\r\n\r\n this.updateAccessTokenSignal(null);\r\n this.updateRefreshTokenSignal(null);\r\n\r\n this.verifiedAccessToken = null;\r\n }\r\n\r\n private updateAccessTokenSignal<T extends Nullable<AccessToken>>(newToken: Nullable<AccessToken>): T {\r\n if (this.accessToken()?.token !== newToken?.token) {\r\n this.accessToken.set(newToken);\r\n }\r\n\r\n return <T>this.accessToken();\r\n }\r\n\r\n private updateRefreshTokenSignal<T extends Nullable<RefreshToken>>(newToken: Nullable<RefreshToken>): T {\r\n if (this.refreshToken()?.token !== newToken?.token) {\r\n this.refreshToken.set(newToken);\r\n }\r\n\r\n return <T>this.refreshToken();\r\n }\r\n\r\n private setToken<T extends IdentityToken>(key: TokenKey, token: unknown): T {\r\n /**\r\n * make sure the provided token is at least a string\r\n */\r\n if (typeof token !== 'string') {\r\n throw new InvalidTokenError(`Provided ${key} value is not a string`);\r\n }\r\n\r\n /**\r\n * get identity token\r\n */\r\n const identityToken = this.wrapToken<T>(token);\r\n\r\n /**\r\n * if provided - save it to local storage\r\n */\r\n if (identityToken) {\r\n localStorage.setItem(key, token);\r\n\r\n /**\r\n * acquisition points verify the RS256 access token BEFORE calling the\r\n * setter (JwksService.verifyNewToken) — record the stored string as\r\n * this page's verified copy so reads stay a sync string compare\r\n */\r\n if (key === TokenKey.accessToken) {\r\n this.verifiedAccessToken = token;\r\n }\r\n }\r\n\r\n LogHelper.log(key, identityToken);\r\n\r\n return identityToken;\r\n }\r\n\r\n private getToken<T extends IdentityToken>(key: TokenKey): Nullable<T> {\r\n /**\r\n * get token value from local storage or use null\r\n */\r\n const actualToken = localStorage.getItem(key) ?? null;\r\n\r\n if (typeof actualToken === 'string') {\r\n try {\r\n /**\r\n * FAIL CLOSED (access token only — refresh tokens are ALWAYS HS256\r\n * by IDMS architecture and take the legacy path below): an RS256\r\n * access token that is not this page's verified copy is not exposed —\r\n * verify it in the background and report \"no token\" meanwhile\r\n * (authenticate() awaits the verdict via whenVerified())\r\n */\r\n if (key === TokenKey.accessToken && !this.isVerified(actualToken)) {\r\n this.verifyInBackground(actualToken);\r\n\r\n return null;\r\n }\r\n\r\n return this.wrapToken<T>(actualToken);\r\n } catch (error) {\r\n LogHelper.error(error);\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n\r\n private isVerified(token: string): boolean {\r\n /**\r\n * only asymmetric (RS256) tokens are verifiable client-side; anything\r\n * else behaves exactly as before this feature existed\r\n */\r\n if (TokenHelper.getHeader(token).alg !== 'RS256') {\r\n return true;\r\n }\r\n\r\n return this.verifiedAccessToken === token;\r\n }\r\n\r\n private verifyInBackground(token: string) {\r\n /**\r\n * one in-flight check per token value; repeated sync reads while the\r\n * verdict is pending must not stack up requests\r\n */\r\n if (this.pendingVerification?.token === token) {\r\n return;\r\n }\r\n\r\n const done = this.jwksService.verifyExistingToken(token).pipe(\r\n /**\r\n * an ERROR from the verification (e.g., malformed token) is treated\r\n * exactly like failed verification — whenVerified() must never error\r\n */\r\n catchError((error: unknown) => {\r\n LogHelper.error(error);\r\n\r\n return of(false);\r\n }),\r\n map((valid) => {\r\n /**\r\n * Clear the slot BEFORE applying the verdict: authenticate() re-reads\r\n * synchronously as the verdict emits, and a still-set slot makes\r\n * hasPendingVerification() loop forever on a token that verifies but\r\n * can't be read (wrong iss/sub). A newer token re-registers after this.\r\n */\r\n if (this.pendingVerification?.token === token) {\r\n this.pendingVerification = null;\r\n }\r\n\r\n return this.applyVerdict(token, valid);\r\n }),\r\n /**\r\n * late whenVerified() subscribers must still observe the settled verdict\r\n */\r\n shareReplay(1),\r\n );\r\n\r\n this.pendingVerification = { token, done };\r\n\r\n /**\r\n * kick the background check off immediately — reads are sync and never\r\n * subscribe themselves\r\n */\r\n done.subscribe();\r\n }\r\n\r\n private applyVerdict(token: string, valid: boolean): void {\r\n /**\r\n * the stored value moved on while we verified (another tab rotated it, or\r\n * clearTokens ran) — this verdict is about a token nobody holds anymore\r\n */\r\n if (localStorage.getItem(TokenKey.accessToken) !== token) {\r\n return;\r\n }\r\n\r\n if (!valid) {\r\n /**\r\n * definite mismatch = tampering; drop everything and force a re-login\r\n * on the next guarded navigation\r\n */\r\n LogHelper.error(new InvalidTokenError('Stored access token failed JWKS signature verification'));\r\n\r\n return this.clearTokens();\r\n }\r\n\r\n /**\r\n * verify-then-adopt: a legit cold reload or another tab rotated the\r\n * token — adopt and re-read so the signal refreshes for reactive\r\n * consumers\r\n */\r\n this.verifiedAccessToken = token;\r\n\r\n this.getAccessToken();\r\n }\r\n\r\n private wrapToken<T extends IdentityToken>(token: string): T {\r\n /**\r\n * parse a token and get its payload\r\n * NOTE: errors will be thrown if the token is expired or has invalid claims\r\n */\r\n const payload = TokenHelper.getPayload(\r\n token,\r\n {\r\n iss: this.getIssuers(),\r\n sub: this.options.application,\r\n },\r\n 60,\r\n );\r\n\r\n /**\r\n * return identity token object\r\n */\r\n return <T>{ token, payload: payload };\r\n }\r\n\r\n private getIssuers(): string[] {\r\n /**\r\n * if we already have issuers - ise them\r\n */\r\n if (this.issuers.length) {\r\n return this.issuers;\r\n }\r\n\r\n /**\r\n * get issuer value from the options\r\n */\r\n const issuer = this.options.serviceIssuer ?? this.options.backendUrl;\r\n\r\n this.issuers = [issuer];\r\n\r\n /**\r\n * make sure we have both variants (with and without \"/api\" suffix) as issuers\r\n * todo \"/api\" suffix verification must be removed when \"/api\" suffix will be removed from the \"issuer\" in IDMS\r\n */\r\n if (issuer.endsWith('/api')) {\r\n this.issuers.push(issuer.substring(0, issuer.lastIndexOf('/api')));\r\n } else {\r\n this.issuers.push(`${issuer}/api`);\r\n }\r\n\r\n return this.issuers;\r\n }\r\n}\r\n","import { TokenKey } from '../types/internal-enums';\r\n\r\n/**\r\n * PKCE (RFC 7636, S256) for the code-flow app launch.\r\n *\r\n * The `code_verifier` is a secret kept in `sessionStorage` across the\r\n * app → IDMS → app round trip (same tab). Only the `code_challenge` (its SHA-256\r\n * hash) travels on the outbound redirect URL. IDMS bakes the challenge into the\r\n * one-time code and verifies the presented verifier at `POST /redeem-code`.\r\n */\r\nexport class PkceHelper {\r\n /**\r\n * 43-char base64url verifier from 32 CSPRNG bytes — inside RFC 7636's 43–128\r\n * range, unreserved charset (base64url ⊂ unreserved).\r\n */\r\n static createVerifier(): string {\r\n const bytes = new Uint8Array(32);\r\n crypto.getRandomValues(bytes);\r\n return PkceHelper.toBase64Url(bytes);\r\n }\r\n\r\n /** `BASE64URL(SHA256(verifier))` — the S256 challenge (RFC 7636 §4.2). */\r\n static async createChallenge(verifier: string): Promise<string> {\r\n const digest = await crypto.subtle.digest(\r\n 'SHA-256',\r\n new TextEncoder().encode(verifier),\r\n );\r\n return PkceHelper.toBase64Url(new Uint8Array(digest));\r\n }\r\n\r\n /**\r\n * Generate a verifier, stash it for the pending `/redeem-code`, and build the\r\n * IDMS login redirect URL carrying the S256 challenge. Returned (not applied)\r\n * so the caller assigns `window.location.href` — keeps this unit testable\r\n * without triggering navigation.\r\n */\r\n static async buildLoginRedirectUrl(\r\n options: { frontendUrl: string; application: string },\r\n redirectPath: string,\r\n ): Promise<string> {\r\n const verifier = PkceHelper.createVerifier();\r\n const challenge = await PkceHelper.createChallenge(verifier);\r\n\r\n sessionStorage.setItem(TokenKey.pkceVerifier, verifier);\r\n\r\n return (\r\n `${options.frontendUrl}/apps/${options.application}` +\r\n `?redirect=true&redirect_path=${encodeURIComponent(redirectPath)}` +\r\n `&code_challenge=${challenge}&code_challenge_method=S256`\r\n );\r\n }\r\n\r\n /** The verifier stashed for the pending redemption (or `null`). */\r\n static readVerifier(): string | null {\r\n return sessionStorage.getItem(TokenKey.pkceVerifier);\r\n }\r\n\r\n static clearVerifier(): void {\r\n sessionStorage.removeItem(TokenKey.pkceVerifier);\r\n }\r\n\r\n private static toBase64Url(bytes: Uint8Array): string {\r\n let binary = '';\r\n for (const byte of bytes) {\r\n binary += String.fromCharCode(byte);\r\n }\r\n return btoa(binary)\r\n .replace(/\\+/g, '-')\r\n .replace(/\\//g, '_')\r\n .replace(/=+$/, '');\r\n }\r\n}\r\n","import { computed, inject, Injectable } from '@angular/core';\r\nimport {\r\n catchError,\r\n finalize,\r\n map,\r\n Observable,\r\n of,\r\n shareReplay,\r\n switchMap,\r\n throwError,\r\n} from 'rxjs';\r\nimport { TokenService } from './token.service';\r\nimport { JwksService } from './jwks.service';\r\nimport { HttpClient } from '@angular/common/http';\r\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\r\nimport { Nullable, RefreshTokenResponse } from '../types/internal-types';\r\nimport { LogHelper } from '../helpers/log.helper';\r\nimport { PkceHelper } from '../helpers/pkce.helper';\r\nimport { AccessToken } from '../types/global-types';\r\nimport { InvalidTokenError } from 'jwt-decode';\r\nimport { ErrorHelper } from '../helpers/error.helper';\r\nimport { IdentityServiceInterface } from '../interfaces/identity-service.interface';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class IdentityService implements IdentityServiceInterface {\r\n /**\r\n * NOTE: we expose access and refresh tokens as computed signals to allow\r\n * library users reactive readonly access to tokens, since \"IdentityService\"\r\n * is the only \"public\" service in the library\r\n */\r\n readonly accessToken = computed(() => this.tokenService.readonlyAccessToken());\r\n readonly refreshToken = computed(() => this.tokenService.readonlyRefreshToken());\r\n\r\n private refreshInProgress: Nullable<Observable<AccessToken>> = null;\r\n\r\n private readonly tokenService = inject(TokenService);\r\n private readonly jwksService = inject(JwksService);\r\n private readonly httpClient = inject(HttpClient);\r\n private readonly options = inject(IDENTITY_OPTIONS);\r\n\r\n isAuthenticated() {\r\n return !!this.tokenService.getAccessToken();\r\n }\r\n\r\n authenticate(): Observable<AccessToken> {\r\n /**\r\n * get access token (the read also starts the background JWKS check when\r\n * the stored RS256 token is not verified yet)\r\n */\r\n const accessToken = this.tokenService.getAccessToken();\r\n\r\n /**\r\n * if it is available - return it (synchronous fast path — unchanged for\r\n * verified/HS256 tokens)\r\n */\r\n if (accessToken) {\r\n return of(accessToken);\r\n }\r\n\r\n /**\r\n * the access token read may be null because a background JWKS\r\n * verification is still in flight (cold reload / cross-tab rotation) —\r\n * ALWAYS wait for that verdict before deciding, and only then fall back\r\n * to the refresh token. Deciding on the refresh token early would turn\r\n * every cold reload of a valid RS256 session into a needless token\r\n * rotation (refresh tokens are always HS256, so they read as available\r\n * while the access verdict is still pending). With no verification\r\n * pending, whenVerified() emits synchronously — legacy timing unchanged.\r\n */\r\n return this.decideWhenQuiescent();\r\n }\r\n\r\n /**\r\n * wait for any in-flight background verification, then decide from FRESH\r\n * reads. Recurses when the deciding re-read itself discovers a newer\r\n * unverified token (cross-tab rotation landing between a settled verdict\r\n * and the re-read — a check whenVerified() could not have covered because\r\n * it did not exist yet); otherwise that read's fail-closed null would be\r\n * mistaken for \"no token\" and trigger a needless rotation. Each recursion\r\n * consumes a real async verdict, so this terminates as soon as rotations\r\n * stop. Legacy HS256 reads never start a check — for them everything here\r\n * stays synchronous and same-tick.\r\n */\r\n private decideWhenQuiescent(): Observable<AccessToken> {\r\n return this.tokenService.whenVerified().pipe(\r\n switchMap(() => {\r\n const verifiedAccessToken = this.tokenService.getAccessToken();\r\n\r\n if (verifiedAccessToken) {\r\n return of(verifiedAccessToken);\r\n }\r\n\r\n if (this.tokenService.hasPendingVerification()) {\r\n return this.decideWhenQuiescent();\r\n }\r\n\r\n if (this.tokenService.getRefreshToken()) {\r\n return this.refreshTokens();\r\n }\r\n\r\n /**\r\n * throw error if no tokens are available\r\n */\r\n return throwError(() => new InvalidTokenError('No valid tokens found'));\r\n }),\r\n );\r\n }\r\n\r\n refreshTokens(): Observable<AccessToken> {\r\n if (this.refreshInProgress) return this.refreshInProgress;\r\n\r\n this.refreshInProgress = this.performRefresh().pipe(\r\n catchError((error: unknown) => {\r\n this.refreshInProgress = null;\r\n return throwError(() => error);\r\n }),\r\n finalize(() => {\r\n this.refreshInProgress = null;\r\n }),\r\n shareReplay(1),\r\n );\r\n\r\n return this.refreshInProgress;\r\n }\r\n\r\n clearTokens() {\r\n this.tokenService.clearTokens();\r\n }\r\n\r\n /**\r\n * exchange a one-time authorization code (delivered in the app callback by a\r\n * code-enabled IDMS app) for the SAME token pair the legacy tokens-in-URL\r\n * callback delivers. Storage is the caller's job (the auth-callback guard),\r\n * mirroring how the guard stores tokens today.\r\n *\r\n * NOTE: `redirect_uri` MUST equal this app's IDMS `loginUrl` — i.e., the\r\n * callback route we are currently on — so IDMS's bind-check on redemption\r\n * passes. We derive it from the current location.\r\n *\r\n * PKCE (RFC 7636): send the `code_verifier` stashed by the launch redirect\r\n * (PkceHelper); IDMS re-hashes it against the code's challenge. Cleared on\r\n * success — it is single-use like the code.\r\n */\r\n redeemCode(code: string): Observable<RefreshTokenResponse> {\r\n /**\r\n * the verifier is single-use — read it and drop it immediately, regardless\r\n * of the redeem outcome. The value is captured in the request body below, so\r\n * a failed/replayed attempt never leaves a spent verifier in sessionStorage.\r\n */\r\n const codeVerifier = PkceHelper.readVerifier() ?? '';\r\n PkceHelper.clearVerifier();\r\n\r\n return this.httpClient\r\n .post<RefreshTokenResponse>(\r\n `${this.options.backendUrl}/api/v${this.options.apiVersion ?? 1}/redeem-code`,\r\n {\r\n code,\r\n client_id: this.options.application,\r\n redirect_uri: `${window.location.origin}${window.location.pathname}`,\r\n code_verifier: codeVerifier,\r\n },\r\n )\r\n .pipe(\r\n switchMap((response) =>\r\n /**\r\n * verify the RS256 access token against a fresh JWKS BEFORE handing\r\n * the pair to the caller (the callback guard stores them); HS256\r\n * access tokens pass through synchronously untouched. The refresh\r\n * token is ALWAYS HS256 (IDMS architecture) and is never verified.\r\n */\r\n this.jwksService.verifyNewToken(response.access_token).pipe(map(() => response)),\r\n ),\r\n );\r\n }\r\n\r\n private performRefresh() {\r\n const refreshToken = this.tokenService.getRefreshToken();\r\n\r\n /**\r\n * throw error if the refresh token is not available\r\n */\r\n if (!refreshToken) {\r\n return throwError(() => new InvalidTokenError('No refresh token found'));\r\n }\r\n\r\n /**\r\n * make a request to Identity service to get new tokens\r\n */\r\n return this.httpClient\r\n .get<RefreshTokenResponse>(`${this.options.backendUrl}/api/v${this.options.apiVersion ?? 1}/refresh-token`, {\r\n headers: { Authorization: `Bearer ${refreshToken.token}` },\r\n })\r\n .pipe(\r\n catchError((error: unknown) => {\r\n /**\r\n * log error if it happens\r\n */\r\n LogHelper.error(\r\n new InvalidTokenError(\r\n `Failed to obtain tokens. Reason: ${ErrorHelper.extractMessage(error).toLowerCase()}`,\r\n ),\r\n );\r\n\r\n return throwError(() => error);\r\n }),\r\n switchMap((response) =>\r\n /**\r\n * \"new access key obtained\" — eagerly refresh the JWKS cache and\r\n * verify the RS256 access token BEFORE anything is stored (the\r\n * refresh token is ALWAYS HS256 and is never verified)\r\n */\r\n this.jwksService.verifyNewToken(response.access_token).pipe(map(() => response)),\r\n ),\r\n map((response) => {\r\n /**\r\n * set refresh token\r\n */\r\n this.tokenService.setRefreshToken(response.refresh_token);\r\n\r\n /**\r\n * set and return access token\r\n */\r\n return this.tokenService.setAccessToken(response.access_token);\r\n }),\r\n );\r\n }\r\n}\r\n","import { CanActivateFn } from '@angular/router';\r\nimport { inject } from '@angular/core';\r\nimport { IdentityService } from '../services/identity.service';\r\nimport { catchError, from, map } from 'rxjs';\r\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\r\nimport { LogHelper } from '../helpers/log.helper';\r\nimport { PkceHelper } from '../helpers/pkce.helper';\r\n\r\nexport const IDENTITY_AUTH_GUARD: CanActivateFn = (_route, state) => {\r\n const identityService = inject(IdentityService);\r\n const options = inject(IDENTITY_OPTIONS);\r\n\r\n /**\r\n * check if the user is authenticated;\r\n * if an error happened - redirect to IDMS url to re-login;\r\n * return true on success and false on error\r\n */\r\n return identityService.authenticate().pipe(\r\n catchError((error) => {\r\n LogHelper.error(error);\r\n\r\n /**\r\n * no session → re-login. PKCE: stash a code_verifier and redirect to IDMS\r\n * with the S256 code_challenge so the code IDMS mints are bound to this\r\n * client (verified back at POST /redeem-code).\r\n */\r\n return from(PkceHelper.buildLoginRedirectUrl(options, state.url)).pipe(\r\n map((url) => {\r\n window.location.href = url;\r\n\r\n return null;\r\n }),\r\n );\r\n }),\r\n map((accessToken) => !!accessToken),\r\n );\r\n};\r\n","import { CanActivateFn, Router } from '@angular/router';\r\nimport { inject } from '@angular/core';\r\nimport { catchError, from, map, of, switchMap } from 'rxjs';\r\nimport { TokenService } from '../services/token.service';\r\nimport { IdentityService } from '../services/identity.service';\r\nimport { JwksService } from '../services/jwks.service';\r\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\r\nimport { LogHelper } from '../helpers/log.helper';\r\nimport { ErrorHelper } from '../helpers/error.helper';\r\nimport { PkceHelper } from '../helpers/pkce.helper';\r\n\r\nexport const IDENTITY_AUTH_CALLBACK_GUARD: CanActivateFn = (route) => {\r\n const router = inject(Router);\r\n const tokenService = inject(TokenService);\r\n const identityService = inject(IdentityService);\r\n const jwksService = inject(JwksService);\r\n const options = inject(IDENTITY_OPTIONS);\r\n\r\n /**\r\n * display an error message on the page and return false to stop further\r\n * navigation; text (not HTML) because the message may embed attacker-controlled\r\n * JWT claim values from the callback query param (innerHTML would be XSS)\r\n */\r\n const denyWithError = (error: unknown) => {\r\n const routerOutlet = document.body.querySelector('router-outlet');\r\n\r\n if (routerOutlet) {\r\n routerOutlet.textContent = ErrorHelper.extractMessage(error);\r\n }\r\n\r\n LogHelper.error(error);\r\n\r\n return false;\r\n };\r\n\r\n const applyTokens = (accessToken: string, refreshToken: string) => {\r\n tokenService.setAccessToken(accessToken);\r\n\r\n tokenService.setRefreshToken(refreshToken);\r\n\r\n return router.navigateByUrl(route.queryParams['redirect_path'] || '/');\r\n };\r\n\r\n const code = route.queryParams['code'];\r\n\r\n /**\r\n * bare launch: IDMS had a session but was launched with no redirect_path, so\r\n * it minted no code and sent us here with `code=none`. Re-login *explicitly*\r\n * with a redirect_path so IDMS mints a real code. The redirect_path MUST be\r\n * non-empty, or IDMS returns `code=none` again (infinite loop) — a bare launch\r\n * has no deep-link target, so falls back to the app root, exactly as the token\r\n * path below does for navigation.\r\n */\r\n if (code === 'none') {\r\n // re-login WITH a redirect_path (loop guard) and, like the auth guard, a\r\n // fresh PKCE challenge so IDMS can mint a code bound to this client.\r\n return from(\r\n PkceHelper.buildLoginRedirectUrl(\r\n options,\r\n route.queryParams['redirect_path'] || '/',\r\n ),\r\n ).pipe(\r\n map((url) => {\r\n window.location.href = url;\r\n\r\n return false;\r\n }),\r\n );\r\n }\r\n\r\n /**\r\n * code-enabled app: exchange the one-time code for the same token pair the\r\n * legacy path receives, store it (as below), and navigate to redirect_path.\r\n */\r\n if (code) {\r\n return identityService.redeemCode(code).pipe(\r\n switchMap(response => applyTokens(response.access_token, response.refresh_token)),\r\n catchError((error: unknown) => of(denyWithError(error))),\r\n );\r\n }\r\n\r\n if (!route.queryParams['access_token'] || !route.queryParams['refresh_token']) {\r\n return denyWithError('No access or refresh token provided');\r\n }\r\n\r\n /**\r\n * legacy tokens-in-URL: verify the RS256 access token against a fresh JWKS\r\n * BEFORE the tokens are stored (an HS256 access token passes through\r\n * synchronously untouched; the refresh token is ALWAYS HS256 by IDMS\r\n * architecture and is never verified). setAccessToken/setRefreshToken still\r\n * decode+validate and throw on an expired/invalid token; both failure kinds\r\n * surface as the error page via catchError (the code branch above gets the\r\n * same treatment).\r\n */\r\n return jwksService.verifyNewToken(route.queryParams['access_token']).pipe(\r\n switchMap(() => applyTokens(route.queryParams['access_token'], route.queryParams['refresh_token'])),\r\n catchError((error: unknown) => of(denyWithError(error))),\r\n );\r\n};\r\n","import { HttpErrorResponse, HttpRequest } from '@angular/common/http';\r\nimport { throwError } from 'rxjs';\r\nimport { Injector } from '@angular/core';\r\nimport { IdentityOptions } from '../types/internal-types';\r\nimport { AccessToken } from '../types/global-types';\r\n\r\nexport class RequestHelper {\r\n static options?: IdentityOptions;\r\n\r\n static isIdentityEndpointRequest(request: HttpRequest<unknown>): boolean {\r\n return (\r\n (!!this.options?.backendUrl && request.url.startsWith(this.options?.backendUrl)) ||\r\n (!!this.options?.frontendUrl && request.url.startsWith(this.options?.frontendUrl))\r\n );\r\n }\r\n\r\n static isAuthorizedRequest(request: HttpRequest<unknown>): boolean {\r\n return !this.options?.requestFilter || this.options.requestFilter(request);\r\n }\r\n\r\n static isAuthorizationError(error: unknown): boolean {\r\n return error instanceof HttpErrorResponse && error.status === 401;\r\n }\r\n\r\n static attachAuthorizationHeader(request: HttpRequest<unknown>, accessToken: AccessToken): HttpRequest<unknown> {\r\n return request.clone({\r\n setHeaders: { authorization: `Bearer ${accessToken.token}`, 'auth-source': 'IDMS' },\r\n });\r\n }\r\n\r\n static onAuthorizationError(error: unknown, injector: Injector) {\r\n if (this.options?.onAuthorizationError) {\r\n this.options?.onAuthorizationError(error, injector);\r\n }\r\n\r\n return throwError(() => error);\r\n }\r\n}\r\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\r\nimport { catchError, Observable, switchMap } from 'rxjs';\r\nimport { inject, Injector } from '@angular/core';\r\nimport { IdentityService } from '../services/identity.service';\r\nimport { RequestHelper } from '../helpers/request.helper';\r\n\r\nexport function requestInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {\r\n const identityService = inject(IdentityService);\r\n const injector = inject(Injector);\r\n\r\n /**\r\n * if it is a request to Identity Service or not an authorized request - do not modify it\r\n */\r\n if (RequestHelper.isIdentityEndpointRequest(request) || !RequestHelper.isAuthorizedRequest(request)) {\r\n return next(request);\r\n }\r\n\r\n /**\r\n * if the current user is authenticated - attach the \"Authorization\" header to the request;\r\n * or process the error if it happens\r\n */\r\n return identityService.authenticate().pipe(\r\n catchError((error: unknown) => RequestHelper.onAuthorizationError(error, injector)),\r\n switchMap((accessToken) => next(RequestHelper.attachAuthorizationHeader(request, accessToken))),\r\n );\r\n}\r\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\r\nimport { catchError, Observable, switchMap, throwError } from 'rxjs';\r\nimport { inject, Injector } from '@angular/core';\r\nimport { IdentityService } from '../services/identity.service';\r\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\r\nimport { RequestHelper } from '../helpers/request.helper';\r\n\r\nexport function responseInterceptor(\r\n request: HttpRequest<unknown>,\r\n next: HttpHandlerFn,\r\n): Observable<HttpEvent<unknown>> {\r\n const options = inject(IDENTITY_OPTIONS);\r\n const identityService = inject(IdentityService);\r\n const injector = inject(Injector);\r\n\r\n /**\r\n * process all response errors\r\n */\r\n return next(request).pipe(\r\n catchError((error: unknown) => {\r\n /**\r\n * if error happened with Identity Service request, or not-authorized request, or it is not an authorization (401) error -\r\n * do not change anything and return the error as is\r\n */\r\n if (\r\n RequestHelper.isIdentityEndpointRequest(request) ||\r\n !RequestHelper.isAuthorizedRequest(request) ||\r\n !RequestHelper.isAuthorizationError(error)\r\n ) {\r\n return throwError(() => error);\r\n }\r\n\r\n /**\r\n * if options do not allow trying and refresh tokens on error 401 -\r\n * process the error and return it\r\n */\r\n if (!options.refreshTokenOnAuthError) {\r\n return RequestHelper.onAuthorizationError(error, injector);\r\n }\r\n\r\n /**\r\n * if tokens were refreshed - attach the \"Authorization\" header to the request and call it again;\r\n * or process the error if it happens\r\n */\r\n return identityService.refreshTokens().pipe(\r\n switchMap((accessToken) => next(RequestHelper.attachAuthorizationHeader(request, accessToken))),\r\n catchError((error: unknown) => RequestHelper.onAuthorizationError(error, injector)),\r\n );\r\n }),\r\n );\r\n}\r\n","import { provideHttpClient, withInterceptors } from '@angular/common/http';\r\nimport { requestInterceptor } from '../interceptors/request.interceptor';\r\nimport { responseInterceptor } from '../interceptors/response.interceptor';\r\nimport { IDENTITY_OPTIONS } from '../types/internal-consts';\r\nimport { LogHelper } from '../helpers/log.helper';\r\nimport { RequestHelper } from '../helpers/request.helper';\r\nimport { IdentityOptions } from '../types/internal-types';\r\n\r\nexport function provideIdentity(options: IdentityOptions) {\r\n LogHelper.options = options;\r\n RequestHelper.options = options;\r\n\r\n return [\r\n { provide: IDENTITY_OPTIONS, useValue: options },\r\n provideHttpClient(withInterceptors([requestInterceptor, responseInterceptor])),\r\n ];\r\n}\r\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ascentgl/ads-identity",
3
- "version": "22.0.0",
3
+ "version": "22.2.0",
4
4
  "peerDependencies": {
5
5
  "jwt-decode": ">=4.0.0"
6
6
  },
@@ -37,6 +37,13 @@ type AccessTokenPayload = IdentityJwtPayload & {
37
37
  email: string;
38
38
  };
39
39
  type RefreshTokenPayload = IdentityJwtPayload;
40
+ type RefreshTokenResponse = {
41
+ access_token: string;
42
+ refresh_token: string;
43
+ token_type: string;
44
+ expires_in: string;
45
+ refresh_token_expires_in: string;
46
+ };
40
47
 
41
48
  type AccessToken = {
42
49
  token: string;
@@ -66,12 +73,15 @@ declare class IdentityService implements IdentityServiceInterface {
66
73
  readonly refreshToken: _angular_core.Signal<Nullable<_ascentgl_ads_identity.RefreshToken>>;
67
74
  private refreshInProgress;
68
75
  private readonly tokenService;
76
+ private readonly jwksService;
69
77
  private readonly httpClient;
70
78
  private readonly options;
71
79
  isAuthenticated(): boolean;
72
80
  authenticate(): Observable<AccessToken>;
81
+ private decideWhenQuiescent;
73
82
  refreshTokens(): Observable<AccessToken>;
74
83
  clearTokens(): void;
84
+ redeemCode(code: string): Observable<RefreshTokenResponse>;
75
85
  private performRefresh;
76
86
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<IdentityService, never>;
77
87
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<IdentityService>;