@masterteam/gateway-auth 0.0.16 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { HttpClient, HttpContextToken, HttpBackend } from '@angular/common/http';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, inject, Injectable, computed, input, ChangeDetectionStrategy, Component, signal, effect } from '@angular/core';
4
- import { EMPTY, of, isObservable, from, firstValueFrom, map, tap as tap$1, shareReplay, finalize, switchMap, catchError as catchError$1, throwError } from 'rxjs';
4
+ import { EMPTY, of, isObservable, from, firstValueFrom, map, tap as tap$1, shareReplay, finalize, catchError as catchError$1, throwError, switchMap } from 'rxjs';
5
5
  import { Action, Selector, State, Store, select } from '@ngxs/store';
6
6
  import { Router, ActivatedRoute } from '@angular/router';
7
7
  import { ModalService } from '@masterteam/components/modal';
@@ -28,9 +28,12 @@ const GATEWAY_AUTH_ENDPOINTS = {
28
28
  resendMfa: 'auth/2fa/resend',
29
29
  refresh: 'auth/refresh',
30
30
  logout: 'auth/logout',
31
+ meApplications: 'auth/me/applications',
32
+ applicationContext: 'public/application-context',
31
33
  ssoProviders: 'auth/sso/providers',
32
34
  ssoExchange: 'auth/sso/exchange',
33
35
  ssoTokenExchange: 'auth/sso/token-exchange',
36
+ applicationLaunch: (applicationCode) => `applications/${encodeURIComponent(applicationCode)}/launch`,
34
37
  nafathStart: (providerKey) => `auth/sso/nafath/${encodeURIComponent(providerKey)}/start`,
35
38
  nafathStatus: (providerKey) => `auth/sso/nafath/${encodeURIComponent(providerKey)}/status`,
36
39
  ssoStart: (providerKey) => `auth/sso/${encodeURIComponent(providerKey)}/start`,
@@ -58,6 +61,11 @@ function resolveAccessTokenRefreshSkewMs(skewMs) {
58
61
  ? skewMs
59
62
  : GATEWAY_AUTH_ACCESS_TOKEN_REFRESH_SKEW_MS;
60
63
  }
64
+ function resolveApplicationCodeOption(applicationCode) {
65
+ const value = typeof applicationCode === 'function' ? applicationCode() : applicationCode;
66
+ const normalized = value?.trim();
67
+ return normalized ? normalized : null;
68
+ }
61
69
  function resolveGatewayDeviceToken(deviceToken) {
62
70
  const configuredToken = typeof deviceToken === 'function' ? deviceToken() : deviceToken;
63
71
  const normalizedConfiguredToken = configuredToken?.trim();
@@ -310,6 +318,53 @@ class SetRateLimit {
310
318
  class ClearRateLimit {
311
319
  static type = '[Auth] Clear Rate Limit';
312
320
  }
321
+ class LoadApplications {
322
+ static type = '[Auth] Load Applications';
323
+ }
324
+ class SetApplications {
325
+ applications;
326
+ static type = '[Auth] Set Applications';
327
+ constructor(applications) {
328
+ this.applications = applications;
329
+ }
330
+ }
331
+ class LaunchApplication {
332
+ applicationCode;
333
+ returnUrl;
334
+ navigate;
335
+ static type = '[Auth] Launch Application';
336
+ constructor(applicationCode, returnUrl, navigate = false) {
337
+ this.applicationCode = applicationCode;
338
+ this.returnUrl = returnUrl;
339
+ this.navigate = navigate;
340
+ }
341
+ }
342
+ class SetAppSession {
343
+ session;
344
+ static type = '[Auth] Set App Session';
345
+ constructor(session) {
346
+ this.session = session;
347
+ }
348
+ }
349
+ class UpdateAppTokens {
350
+ applicationCode;
351
+ tokens;
352
+ static type = '[Auth] Update App Tokens';
353
+ constructor(applicationCode, tokens) {
354
+ this.applicationCode = applicationCode;
355
+ this.tokens = tokens;
356
+ }
357
+ }
358
+ class ClearAppSession {
359
+ applicationCode;
360
+ static type = '[Auth] Clear App Session';
361
+ constructor(applicationCode) {
362
+ this.applicationCode = applicationCode;
363
+ }
364
+ }
365
+ class ClearAllAppSessions {
366
+ static type = '[Auth] Clear All App Sessions';
367
+ }
313
368
 
314
369
  const GATEWAY_AUTH_OPTIONS = new InjectionToken('GATEWAY_AUTH_OPTIONS', {
315
370
  factory: () => ({
@@ -321,6 +376,7 @@ const GATEWAY_AUTH_OPTIONS = new InjectionToken('GATEWAY_AUTH_OPTIONS', {
321
376
  ssoCallbackPath: '/auth/sso/callback',
322
377
  defaultAuthenticatedRoute: '/',
323
378
  preserveSsoProvidersOnLogout: true,
379
+ autoLaunchApplicationOnLogin: false,
324
380
  loginPage: {
325
381
  imageBasePath: '/Settings/login-image/',
326
382
  translationPrefix: 'login',
@@ -378,6 +434,10 @@ const AUTH_STATE_DEFAULTS = {
378
434
  pendingMfa: null,
379
435
  ssoProviders: [],
380
436
  rateLimit: null,
437
+ applications: [],
438
+ applicationsLoading: false,
439
+ appSessions: {},
440
+ appLaunchLoading: {},
381
441
  };
382
442
  function pruneRateLimit(rateLimit) {
383
443
  if (!rateLimit) {
@@ -389,6 +449,22 @@ function pruneRateLimit(rateLimit) {
389
449
  }
390
450
  return rateLimit;
391
451
  }
452
+ function pruneAppSessions(appSessions) {
453
+ if (!appSessions || typeof appSessions !== 'object') {
454
+ return {};
455
+ }
456
+ const pruned = {};
457
+ for (const [code, session] of Object.entries(appSessions)) {
458
+ if (!session || typeof session !== 'object') {
459
+ continue;
460
+ }
461
+ if (!session.accessToken || !session.refreshToken) {
462
+ continue;
463
+ }
464
+ pruned[code] = session;
465
+ }
466
+ return pruned;
467
+ }
392
468
  function sanitizePersistedAuthState(obj) {
393
469
  return {
394
470
  ...AUTH_STATE_DEFAULTS,
@@ -402,6 +478,10 @@ function sanitizePersistedAuthState(obj) {
402
478
  pendingMfa: null,
403
479
  ssoProviders: [],
404
480
  rateLimit: pruneRateLimit(obj?.rateLimit ?? null),
481
+ applications: obj?.applications ?? [],
482
+ applicationsLoading: false,
483
+ appSessions: pruneAppSessions(obj?.appSessions ?? null),
484
+ appLaunchLoading: {},
405
485
  };
406
486
  }
407
487
 
@@ -465,6 +545,18 @@ let GatewayAuthState = class GatewayAuthState {
465
545
  static userDetails(state) {
466
546
  return state.user?.user || null;
467
547
  }
548
+ static applications(state) {
549
+ return state.applications;
550
+ }
551
+ static applicationsLoading(state) {
552
+ return state.applicationsLoading;
553
+ }
554
+ static appSessions(state) {
555
+ return state.appSessions;
556
+ }
557
+ static appLaunchLoading(state) {
558
+ return state.appLaunchLoading;
559
+ }
468
560
  login(ctx, action) {
469
561
  if (this.isRateLimitActive(ctx, 'login')) {
470
562
  return EMPTY;
@@ -475,10 +567,14 @@ let GatewayAuthState = class GatewayAuthState {
475
567
  pendingMfa: null,
476
568
  twoFactorRequired: false,
477
569
  });
570
+ const applicationCode = action.payload.applicationCode ??
571
+ resolveApplicationCodeOption(this.options.applicationCode) ??
572
+ undefined;
478
573
  return this.http
479
574
  .post(this.gatewayAuthMutationUrl(GATEWAY_AUTH_ENDPOINTS.login), {
480
575
  userName: action.payload.userName,
481
576
  password: action.payload.password,
577
+ applicationCode,
482
578
  isEncrypted: action.payload.isEncrypted ?? false,
483
579
  deviceToken: action.payload.deviceToken || this.deviceToken,
484
580
  recaptchaToken: action.payload.recaptchaToken,
@@ -637,6 +733,9 @@ let GatewayAuthState = class GatewayAuthState {
637
733
  refreshTokenExpiresAt: null,
638
734
  pendingMfa: null,
639
735
  twoFactorRequired: false,
736
+ applications: [],
737
+ appSessions: {},
738
+ appLaunchLoading: {},
640
739
  });
641
740
  }
642
741
  logout(ctx, action) {
@@ -710,6 +809,125 @@ let GatewayAuthState = class GatewayAuthState {
710
809
  clearRateLimit(ctx) {
711
810
  ctx.patchState({ rateLimit: null });
712
811
  }
812
+ loadApplications(ctx) {
813
+ ctx.patchState({ applicationsLoading: true });
814
+ return this.http
815
+ .get(this.gatewayUrl(GATEWAY_AUTH_ENDPOINTS.meApplications))
816
+ .pipe(tap((response) => {
817
+ ctx.patchState({
818
+ applications: response.data?.applications ?? [],
819
+ applicationsLoading: false,
820
+ });
821
+ }), catchError((error) => {
822
+ ctx.patchState({
823
+ applicationsLoading: false,
824
+ error: getGatewayErrorMessage(error, 'Failed to load applications'),
825
+ });
826
+ return of(null);
827
+ }));
828
+ }
829
+ setApplications(ctx, action) {
830
+ ctx.patchState({ applications: action.applications });
831
+ }
832
+ launchApplication(ctx, action) {
833
+ const code = action.applicationCode;
834
+ ctx.patchState({
835
+ appLaunchLoading: {
836
+ ...ctx.getState().appLaunchLoading,
837
+ [code]: true,
838
+ },
839
+ });
840
+ const params = {};
841
+ if (action.returnUrl) {
842
+ params['returnUrl'] = action.returnUrl;
843
+ }
844
+ const deviceToken = this.deviceToken;
845
+ if (deviceToken) {
846
+ params['deviceToken'] = deviceToken;
847
+ }
848
+ return this.http
849
+ .get(this.gatewayUrl(GATEWAY_AUTH_ENDPOINTS.applicationLaunch(code)), { params })
850
+ .pipe(tap((response) => {
851
+ const data = response.data;
852
+ if (!data || !hasGatewayTokens(data.tokens)) {
853
+ ctx.patchState({
854
+ appLaunchLoading: this.removeLoadingFlag(ctx, code),
855
+ });
856
+ return;
857
+ }
858
+ const mapped = mapGatewayTokens(data.tokens);
859
+ const session = {
860
+ applicationCode: data.applicationCode,
861
+ applicationName: data.applicationName,
862
+ launchUrl: data.launchUrl,
863
+ accessToken: mapped.accessToken,
864
+ refreshToken: mapped.refreshToken,
865
+ accessTokenExpiresAt: mapped.accessTokenExpiresAt,
866
+ refreshTokenExpiresAt: mapped.refreshTokenExpiresAt,
867
+ };
868
+ ctx.patchState({
869
+ appSessions: {
870
+ ...ctx.getState().appSessions,
871
+ [data.applicationCode]: session,
872
+ },
873
+ appLaunchLoading: this.removeLoadingFlag(ctx, code),
874
+ });
875
+ if (action.navigate && session.launchUrl) {
876
+ try {
877
+ window.location.assign(session.launchUrl);
878
+ }
879
+ catch {
880
+ // ignore navigation errors in non-browser env
881
+ }
882
+ }
883
+ }), catchError((error) => {
884
+ ctx.patchState({
885
+ appLaunchLoading: this.removeLoadingFlag(ctx, code),
886
+ error: getGatewayErrorMessage(error, 'Failed to launch application'),
887
+ });
888
+ return of(null);
889
+ }));
890
+ }
891
+ setAppSession(ctx, action) {
892
+ ctx.patchState({
893
+ appSessions: {
894
+ ...ctx.getState().appSessions,
895
+ [action.session.applicationCode]: action.session,
896
+ },
897
+ });
898
+ }
899
+ updateAppTokens(ctx, action) {
900
+ const existing = ctx.getState().appSessions[action.applicationCode];
901
+ if (!existing) {
902
+ return;
903
+ }
904
+ const mapped = mapGatewayTokens(action.tokens);
905
+ ctx.patchState({
906
+ appSessions: {
907
+ ...ctx.getState().appSessions,
908
+ [action.applicationCode]: {
909
+ ...existing,
910
+ accessToken: mapped.accessToken,
911
+ refreshToken: mapped.refreshToken,
912
+ accessTokenExpiresAt: mapped.accessTokenExpiresAt,
913
+ refreshTokenExpiresAt: mapped.refreshTokenExpiresAt,
914
+ },
915
+ },
916
+ });
917
+ }
918
+ clearAppSession(ctx, action) {
919
+ const sessions = { ...ctx.getState().appSessions };
920
+ delete sessions[action.applicationCode];
921
+ ctx.patchState({ appSessions: sessions });
922
+ }
923
+ clearAllAppSessions(ctx) {
924
+ ctx.patchState({ appSessions: {}, applications: [] });
925
+ }
926
+ removeLoadingFlag(ctx, code) {
927
+ const next = { ...ctx.getState().appLaunchLoading };
928
+ delete next[code];
929
+ return next;
930
+ }
713
931
  isRateLimitActive(ctx, scope) {
714
932
  const lock = ctx.getState().rateLimit;
715
933
  if (!lock || lock.scope !== scope) {
@@ -773,13 +991,25 @@ let GatewayAuthState = class GatewayAuthState {
773
991
  error: null,
774
992
  pendingMfa: null,
775
993
  twoFactorRequired: false,
994
+ appSessions: {},
995
+ appLaunchLoading: {},
776
996
  });
777
- return this.toObservable(this.options.afterLogin?.(session, ctx)).pipe(tap(() => {
997
+ return this.toObservable(this.options.afterLogin?.(session, ctx)).pipe(mergeMap(() => this.maybeAutoLaunchApplication(ctx)), tap(() => {
778
998
  this.router.navigateByUrl(this.resolveAuthenticatedRoute(), {
779
999
  replaceUrl: true,
780
1000
  });
781
1001
  }));
782
1002
  }
1003
+ maybeAutoLaunchApplication(ctx) {
1004
+ if (!this.options.autoLaunchApplicationOnLogin) {
1005
+ return of(null);
1006
+ }
1007
+ const code = resolveApplicationCodeOption(this.options.applicationCode);
1008
+ if (!code) {
1009
+ return of(null);
1010
+ }
1011
+ return ctx.dispatch(new LaunchApplication(code));
1012
+ }
783
1013
  get deviceToken() {
784
1014
  return resolveGatewayDeviceToken(this.options.deviceToken);
785
1015
  }
@@ -874,6 +1104,27 @@ __decorate([
874
1104
  __decorate([
875
1105
  Action(ClearRateLimit)
876
1106
  ], GatewayAuthState.prototype, "clearRateLimit", null);
1107
+ __decorate([
1108
+ Action(LoadApplications)
1109
+ ], GatewayAuthState.prototype, "loadApplications", null);
1110
+ __decorate([
1111
+ Action(SetApplications)
1112
+ ], GatewayAuthState.prototype, "setApplications", null);
1113
+ __decorate([
1114
+ Action(LaunchApplication)
1115
+ ], GatewayAuthState.prototype, "launchApplication", null);
1116
+ __decorate([
1117
+ Action(SetAppSession)
1118
+ ], GatewayAuthState.prototype, "setAppSession", null);
1119
+ __decorate([
1120
+ Action(UpdateAppTokens)
1121
+ ], GatewayAuthState.prototype, "updateAppTokens", null);
1122
+ __decorate([
1123
+ Action(ClearAppSession)
1124
+ ], GatewayAuthState.prototype, "clearAppSession", null);
1125
+ __decorate([
1126
+ Action(ClearAllAppSessions)
1127
+ ], GatewayAuthState.prototype, "clearAllAppSessions", null);
877
1128
  __decorate([
878
1129
  Selector()
879
1130
  ], GatewayAuthState, "user", null);
@@ -922,6 +1173,18 @@ __decorate([
922
1173
  __decorate([
923
1174
  Selector()
924
1175
  ], GatewayAuthState, "userDetails", null);
1176
+ __decorate([
1177
+ Selector()
1178
+ ], GatewayAuthState, "applications", null);
1179
+ __decorate([
1180
+ Selector()
1181
+ ], GatewayAuthState, "applicationsLoading", null);
1182
+ __decorate([
1183
+ Selector()
1184
+ ], GatewayAuthState, "appSessions", null);
1185
+ __decorate([
1186
+ Selector()
1187
+ ], GatewayAuthState, "appLaunchLoading", null);
925
1188
  GatewayAuthState = __decorate([
926
1189
  State({
927
1190
  name: 'auth',
@@ -930,7 +1193,7 @@ GatewayAuthState = __decorate([
930
1193
  ], GatewayAuthState);
931
1194
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GatewayAuthState, decorators: [{
932
1195
  type: Injectable
933
- }], propDecorators: { login: [], verifyMfa: [], resendMfa: [], loadSsoProviders: [], startSso: [], exchangeSsoCode: [], loginSuccess: [], loginFailure: [], logout: [], updateUserData: [], updateTokens: [], clearError: [], clearPendingMfa: [], setRateLimit: [], clearRateLimit: [] } });
1196
+ }], propDecorators: { login: [], verifyMfa: [], resendMfa: [], loadSsoProviders: [], startSso: [], exchangeSsoCode: [], loginSuccess: [], loginFailure: [], logout: [], updateUserData: [], updateTokens: [], clearError: [], clearPendingMfa: [], setRateLimit: [], clearRateLimit: [], loadApplications: [], setApplications: [], launchApplication: [], setAppSession: [], updateAppTokens: [], clearAppSession: [], clearAllAppSessions: [] } });
934
1197
 
935
1198
  class GatewayAuthFacade {
936
1199
  store = inject(Store);
@@ -950,6 +1213,10 @@ class GatewayAuthFacade {
950
1213
  rateLimit = select(GatewayAuthState.rateLimit);
951
1214
  isAdmin = select(GatewayAuthState.isAdmin);
952
1215
  userDetails = select(GatewayAuthState.userDetails);
1216
+ applications = select(GatewayAuthState.applications);
1217
+ applicationsLoading = select(GatewayAuthState.applicationsLoading);
1218
+ appSessions = select(GatewayAuthState.appSessions);
1219
+ appLaunchLoading = select(GatewayAuthState.appLaunchLoading);
953
1220
  hasError = computed(() => this.error() !== null, ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
954
1221
  isReady = computed(() => !this.loading() && this.error() === null, ...(ngDevMode ? [{ debugName: "isReady" }] : /* istanbul ignore next */ []));
955
1222
  userDisplayName = computed(() => this.user()?.user?.displayName || '', ...(ngDevMode ? [{ debugName: "userDisplayName" }] : /* istanbul ignore next */ []));
@@ -993,6 +1260,34 @@ class GatewayAuthFacade {
993
1260
  updateTokens(tokens) {
994
1261
  this.store.dispatch(new UpdateTokens(tokens));
995
1262
  }
1263
+ loadApplications() {
1264
+ this.store.dispatch(new LoadApplications());
1265
+ }
1266
+ setApplications(applications) {
1267
+ this.store.dispatch(new SetApplications(applications));
1268
+ }
1269
+ launchApplication(applicationCode, returnUrl, navigate = false) {
1270
+ this.store.dispatch(new LaunchApplication(applicationCode, returnUrl, navigate));
1271
+ }
1272
+ setAppSession(session) {
1273
+ this.store.dispatch(new SetAppSession(session));
1274
+ }
1275
+ updateAppTokens(applicationCode, tokens) {
1276
+ this.store.dispatch(new UpdateAppTokens(applicationCode, tokens));
1277
+ }
1278
+ clearAppSession(applicationCode) {
1279
+ this.store.dispatch(new ClearAppSession(applicationCode));
1280
+ }
1281
+ clearAllAppSessions() {
1282
+ this.store.dispatch(new ClearAllAppSessions());
1283
+ }
1284
+ getAppSession(applicationCode) {
1285
+ return this.appSessions()?.[applicationCode] ?? null;
1286
+ }
1287
+ getAppToken(applicationCode) {
1288
+ const session = this.getAppSession(applicationCode);
1289
+ return session?.accessToken ?? null;
1290
+ }
996
1291
  clearError() {
997
1292
  this.store.dispatch(new ClearError());
998
1293
  }
@@ -1064,7 +1359,6 @@ function isGatewayAuthRequestUrl(url, gatewayApiBaseUrl) {
1064
1359
  return (GATEWAY_AUTH_ENDPOINT_PATHS.has(path) ||
1065
1360
  GATEWAY_AUTH_ENDPOINT_PREFIXES.some((prefix) => path.startsWith(prefix)));
1066
1361
  }
1067
- let inflightRefresh$ = null;
1068
1362
  const getBrowserRefreshLock = () => {
1069
1363
  if (typeof navigator === 'undefined') {
1070
1364
  return null;
@@ -1072,12 +1366,12 @@ const getBrowserRefreshLock = () => {
1072
1366
  const locks = navigator.locks;
1073
1367
  return typeof locks?.request === 'function' ? locks : null;
1074
1368
  };
1075
- const runWithRefreshLock = (operation) => {
1369
+ const runWithRefreshLock = (scopeKey, operation) => {
1076
1370
  const locks = getBrowserRefreshLock();
1077
- return locks
1078
- ? locks.request('masterteam-gateway-auth-refresh', operation)
1079
- : operation();
1371
+ return locks ? locks.request(scopeKey, operation) : operation();
1080
1372
  };
1373
+ const GATEWAY_REFRESH_SCOPE = 'gateway';
1374
+ const inflightRefreshByScope = new Map();
1081
1375
  const getCurrentAuthTokens = (auth) => {
1082
1376
  const accessToken = auth.token();
1083
1377
  const refreshToken = auth.refreshToken();
@@ -1099,7 +1393,7 @@ const tokenHasUsableAccessToken = (tokens, refreshSkewMs) => !!tokens.accessToke
1099
1393
  !isExpired(resolveApiDateValue(tokens.accessTokenExpiresAt), refreshSkewMs);
1100
1394
  const tokenHasUsableRefreshToken = (tokens) => !!tokens.refreshToken &&
1101
1395
  !isExpired(resolveApiDateValue(tokens.refreshTokenExpiresAt));
1102
- const getSynchronizedTokens = (auth, staleRefreshToken, refreshSkewMs) => {
1396
+ const getSynchronizedGatewayTokens = (auth, staleRefreshToken, refreshSkewMs) => {
1103
1397
  const candidates = [
1104
1398
  getCurrentAuthTokens(auth),
1105
1399
  readPersistedGatewayAuthTokens(),
@@ -1108,7 +1402,7 @@ const getSynchronizedTokens = (auth, staleRefreshToken, refreshSkewMs) => {
1108
1402
  tokens.refreshToken !== staleRefreshToken &&
1109
1403
  tokenHasUsableAccessToken(tokens, refreshSkewMs)) ?? null);
1110
1404
  };
1111
- const getLatestRefreshToken = (auth, fallbackRefreshToken) => {
1405
+ const getLatestGatewayRefreshToken = (auth, fallbackRefreshToken) => {
1112
1406
  const candidates = [
1113
1407
  getCurrentAuthTokens(auth),
1114
1408
  readPersistedGatewayAuthTokens(),
@@ -1118,33 +1412,95 @@ const getLatestRefreshToken = (auth, fallbackRefreshToken) => {
1118
1412
  tokenHasUsableRefreshToken(tokens));
1119
1413
  return latestTokens?.refreshToken ?? fallbackRefreshToken;
1120
1414
  };
1121
- const executeRefresh = (http, gatewayApiBaseUrl, refreshToken, auth, deviceToken, refreshSkewMs) => runWithRefreshLock(async () => {
1122
- const synchronizedTokens = getSynchronizedTokens(auth, refreshToken, refreshSkewMs);
1123
- if (synchronizedTokens) {
1124
- auth.updateTokens(synchronizedTokens);
1125
- return synchronizedTokens;
1126
- }
1127
- const latestRefreshToken = getLatestRefreshToken(auth, refreshToken);
1128
- const url = buildGatewayUrl(gatewayApiBaseUrl, GATEWAY_AUTH_ENDPOINTS.refresh);
1129
- return firstValueFrom(http
1130
- .post(url, {
1131
- refreshToken: latestRefreshToken,
1132
- deviceToken,
1133
- })
1134
- .pipe(map((response) => response.data), map((tokens) => {
1135
- if (!tokens?.accessToken || !tokens.refreshToken) {
1136
- throw new Error('Invalid refresh response');
1137
- }
1138
- return tokens;
1139
- }), tap$1((tokens) => auth.updateTokens(tokens))));
1415
+ const sessionAsTokens = (session) => ({
1416
+ accessToken: session.accessToken,
1417
+ accessTokenExpiresAt: { actualValue: session.accessTokenExpiresAt ?? '' },
1418
+ refreshToken: session.refreshToken,
1419
+ refreshTokenExpiresAt: { actualValue: session.refreshTokenExpiresAt ?? '' },
1140
1420
  });
1141
- const refreshTokens$ = (http, gatewayApiBaseUrl, refreshToken, auth, deviceToken, refreshSkewMs) => {
1142
- if (!inflightRefresh$) {
1143
- inflightRefresh$ = from(executeRefresh(http, gatewayApiBaseUrl, refreshToken, auth, deviceToken, refreshSkewMs)).pipe(shareReplay({ bufferSize: 1, refCount: true }), finalize(() => {
1144
- inflightRefresh$ = null;
1145
- }));
1421
+ const getSynchronizedAppTokens = (auth, applicationCode, staleRefreshToken, refreshSkewMs) => {
1422
+ const session = auth.getAppSession(applicationCode);
1423
+ if (!session) {
1424
+ return null;
1146
1425
  }
1147
- return inflightRefresh$;
1426
+ const tokens = sessionAsTokens(session);
1427
+ if (tokens.refreshToken !== staleRefreshToken &&
1428
+ tokenHasUsableAccessToken(tokens, refreshSkewMs)) {
1429
+ return tokens;
1430
+ }
1431
+ return null;
1432
+ };
1433
+ const getLatestAppRefreshToken = (auth, applicationCode, fallbackRefreshToken) => {
1434
+ const session = auth.getAppSession(applicationCode);
1435
+ if (!session) {
1436
+ return fallbackRefreshToken;
1437
+ }
1438
+ if (session.refreshToken &&
1439
+ session.refreshToken !== fallbackRefreshToken &&
1440
+ !isExpired(session.refreshTokenExpiresAt)) {
1441
+ return session.refreshToken;
1442
+ }
1443
+ return fallbackRefreshToken;
1444
+ };
1445
+ const executeRefresh = (context, refreshToken) => {
1446
+ const { http, gatewayApiBaseUrl, auth, deviceToken, refreshSkewMs, scope, applicationCode, } = context;
1447
+ const lockKey = scope === 'gateway'
1448
+ ? 'masterteam-gateway-auth-refresh:gateway'
1449
+ : `masterteam-gateway-auth-refresh:app:${applicationCode ?? ''}`;
1450
+ return runWithRefreshLock(lockKey, async () => {
1451
+ if (scope === 'gateway') {
1452
+ const synchronizedTokens = getSynchronizedGatewayTokens(auth, refreshToken, refreshSkewMs);
1453
+ if (synchronizedTokens) {
1454
+ auth.updateTokens(synchronizedTokens);
1455
+ return synchronizedTokens;
1456
+ }
1457
+ }
1458
+ else if (applicationCode) {
1459
+ const synchronizedTokens = getSynchronizedAppTokens(auth, applicationCode, refreshToken, refreshSkewMs);
1460
+ if (synchronizedTokens) {
1461
+ auth.updateAppTokens(applicationCode, synchronizedTokens);
1462
+ return synchronizedTokens;
1463
+ }
1464
+ }
1465
+ const latestRefreshToken = scope === 'gateway'
1466
+ ? getLatestGatewayRefreshToken(auth, refreshToken)
1467
+ : applicationCode
1468
+ ? getLatestAppRefreshToken(auth, applicationCode, refreshToken)
1469
+ : refreshToken;
1470
+ const url = buildGatewayUrl(gatewayApiBaseUrl, GATEWAY_AUTH_ENDPOINTS.refresh);
1471
+ return firstValueFrom(http
1472
+ .post(url, {
1473
+ refreshToken: latestRefreshToken,
1474
+ deviceToken,
1475
+ })
1476
+ .pipe(map((response) => response.data), map((tokens) => {
1477
+ if (!tokens?.accessToken || !tokens.refreshToken) {
1478
+ throw new Error('Invalid refresh response');
1479
+ }
1480
+ return tokens;
1481
+ }), tap$1((tokens) => {
1482
+ if (scope === 'gateway') {
1483
+ auth.updateTokens(tokens);
1484
+ }
1485
+ else if (applicationCode) {
1486
+ auth.updateAppTokens(applicationCode, tokens);
1487
+ }
1488
+ })));
1489
+ });
1490
+ };
1491
+ const refreshTokens$ = (context, refreshToken) => {
1492
+ const scopeKey = context.scope === 'gateway'
1493
+ ? GATEWAY_REFRESH_SCOPE
1494
+ : `application:${context.applicationCode ?? ''}`;
1495
+ const existing = inflightRefreshByScope.get(scopeKey);
1496
+ if (existing) {
1497
+ return existing;
1498
+ }
1499
+ const observable = from(executeRefresh(context, refreshToken)).pipe(shareReplay({ bufferSize: 1, refCount: true }), finalize(() => {
1500
+ inflightRefreshByScope.delete(scopeKey);
1501
+ }));
1502
+ inflightRefreshByScope.set(scopeKey, observable);
1503
+ return observable;
1148
1504
  };
1149
1505
  const prepareRequest = (req, token, markRetried, baseUrl) => {
1150
1506
  let modifiedReq = req;
@@ -1167,6 +1523,35 @@ const prepareRequest = (req, token, markRetried, baseUrl) => {
1167
1523
  }
1168
1524
  return modifiedReq;
1169
1525
  };
1526
+ const resolveRequestScope = (req, options, auth) => {
1527
+ const useGatewayBaseUrl = options.shouldUseGatewayApiBaseUrl?.(req) ?? false;
1528
+ const explicitCode = options.resolveApplicationCodeForRequest?.(req);
1529
+ if (explicitCode) {
1530
+ const session = auth.getAppSession(explicitCode);
1531
+ return {
1532
+ scope: 'application',
1533
+ applicationCode: explicitCode,
1534
+ session,
1535
+ useGatewayBaseUrl,
1536
+ };
1537
+ }
1538
+ if (useGatewayBaseUrl) {
1539
+ return { scope: 'gateway', useGatewayBaseUrl: true };
1540
+ }
1541
+ const defaultCode = resolveApplicationCodeOption(options.applicationCode);
1542
+ if (defaultCode) {
1543
+ const session = auth.getAppSession(defaultCode);
1544
+ if (session) {
1545
+ return {
1546
+ scope: 'application',
1547
+ applicationCode: defaultCode,
1548
+ session,
1549
+ useGatewayBaseUrl,
1550
+ };
1551
+ }
1552
+ }
1553
+ return { scope: 'gateway', useGatewayBaseUrl };
1554
+ };
1170
1555
  const gatewayAuthInterceptor = (req, next) => {
1171
1556
  if (isAssetRequest(req.url)) {
1172
1557
  return next(req);
@@ -1176,41 +1561,86 @@ const gatewayAuthInterceptor = (req, next) => {
1176
1561
  const http = new HttpClient(inject(HttpBackend));
1177
1562
  const gatewayApiBaseUrl = options.getGatewayApiBaseUrl();
1178
1563
  const appApiBaseUrl = options.getApplicationApiBaseUrl?.();
1179
- const useGatewayBaseUrl = options.shouldUseGatewayApiBaseUrl?.(req) ?? false;
1180
- const baseUrl = useGatewayBaseUrl ? gatewayApiBaseUrl : appApiBaseUrl;
1181
1564
  const deviceToken = resolveGatewayDeviceToken(options.deviceToken);
1182
1565
  const refreshSkewMs = resolveAccessTokenRefreshSkewMs(options.accessTokenRefreshSkewMs);
1183
- const accessToken = auth.token();
1184
- const refreshToken = auth.refreshToken();
1185
- const accessTokenExpiresAt = auth.accessTokenExpiresAt();
1186
- const refreshTokenExpiresAt = auth.refreshTokenExpiresAt();
1566
+ const isAuthRequest = isGatewayAuthRequestUrl(req.url, gatewayApiBaseUrl);
1567
+ const alreadyRetried = req.context.get(GATEWAY_AUTH_RETRY_CONTEXT);
1568
+ const resolved = resolveRequestScope(req, options, auth);
1569
+ const baseUrl = resolved.useGatewayBaseUrl
1570
+ ? gatewayApiBaseUrl
1571
+ : appApiBaseUrl;
1572
+ const gatewayAccessToken = auth.token();
1573
+ const gatewayRefreshToken = auth.refreshToken();
1574
+ const gatewayAccessTokenExpiresAt = auth.accessTokenExpiresAt();
1575
+ const gatewayRefreshTokenExpiresAt = auth.refreshTokenExpiresAt();
1576
+ const session = resolved.session ?? null;
1577
+ const accessToken = resolved.scope === 'application' && session
1578
+ ? session.accessToken
1579
+ : gatewayAccessToken;
1580
+ const accessTokenExpiresAt = resolved.scope === 'application' && session
1581
+ ? session.accessTokenExpiresAt
1582
+ : gatewayAccessTokenExpiresAt;
1583
+ const refreshToken = resolved.scope === 'application' && session
1584
+ ? session.refreshToken
1585
+ : gatewayRefreshToken;
1586
+ const refreshTokenExpiresAt = resolved.scope === 'application' && session
1587
+ ? session.refreshTokenExpiresAt
1588
+ : gatewayRefreshTokenExpiresAt;
1187
1589
  const accessTokenValid = !!accessToken && !isExpired(accessTokenExpiresAt, refreshSkewMs);
1188
1590
  const canRefresh = !!refreshToken && !isExpired(refreshTokenExpiresAt);
1189
- const alreadyRetried = req.context.get(GATEWAY_AUTH_RETRY_CONTEXT);
1190
- const isAuthRequest = isGatewayAuthRequestUrl(req.url, gatewayApiBaseUrl);
1191
1591
  const tokenToAttach = !isAuthRequest && accessTokenValid ? accessToken : null;
1192
- if (!isAuthRequest && !accessTokenValid && canRefresh) {
1193
- return refreshTokens$(http, gatewayApiBaseUrl, refreshToken, auth, deviceToken, refreshSkewMs).pipe(switchMap((tokens) => next(prepareRequest(req, tokens.accessToken, true, baseUrl))), catchError$1((error) => {
1194
- auth.logout();
1195
- return throwError(() => error);
1196
- }));
1592
+ const buildRefreshContext = (scopeOverride, appCodeOverride) => ({
1593
+ http,
1594
+ gatewayApiBaseUrl,
1595
+ auth,
1596
+ deviceToken,
1597
+ refreshSkewMs,
1598
+ scope: scopeOverride ?? resolved.scope,
1599
+ applicationCode: appCodeOverride ?? resolved.applicationCode,
1600
+ });
1601
+ if (!isAuthRequest && !accessTokenValid && canRefresh && refreshToken) {
1602
+ return refreshTokens$(buildRefreshContext(), refreshToken).pipe(catchError$1((refreshError) => {
1603
+ if (resolved.scope === 'application' && resolved.applicationCode) {
1604
+ auth.clearAppSession(resolved.applicationCode);
1605
+ }
1606
+ else {
1607
+ auth.logout();
1608
+ }
1609
+ return throwError(() => refreshError);
1610
+ }), switchMap((tokens) => next(prepareRequest(req, tokens.accessToken, true, baseUrl))));
1197
1611
  }
1198
1612
  return next(prepareRequest(req, tokenToAttach, false, baseUrl)).pipe(catchError$1((error) => {
1199
- if (error?.status === 401 && !isAuthRequest && !alreadyRetried) {
1200
- const latestRefreshToken = auth.refreshToken();
1201
- const latestRefreshTokenExpiresAt = auth.refreshTokenExpiresAt();
1202
- const canRefreshNow = !!latestRefreshToken && !isExpired(latestRefreshTokenExpiresAt);
1203
- if (canRefreshNow) {
1204
- return refreshTokens$(http, gatewayApiBaseUrl, latestRefreshToken, auth, deviceToken, refreshSkewMs).pipe(switchMap((tokens) => next(prepareRequest(req, tokens.accessToken, true, baseUrl))), catchError$1((refreshError) => {
1205
- auth.logout();
1206
- return throwError(() => refreshError);
1207
- }));
1208
- }
1613
+ if (error?.status !== 401 || isAuthRequest || alreadyRetried) {
1614
+ return throwError(() => error);
1209
1615
  }
1210
- if (error?.status === 401 && !isAuthRequest) {
1211
- auth.logout();
1616
+ const latestAppSession = resolved.scope === 'application' && resolved.applicationCode
1617
+ ? auth.getAppSession(resolved.applicationCode)
1618
+ : null;
1619
+ const latestRefreshToken = resolved.scope === 'application' && resolved.applicationCode
1620
+ ? (latestAppSession?.refreshToken ?? null)
1621
+ : auth.refreshToken();
1622
+ const latestRefreshTokenExpiresAt = resolved.scope === 'application' && resolved.applicationCode
1623
+ ? (latestAppSession?.refreshTokenExpiresAt ?? null)
1624
+ : auth.refreshTokenExpiresAt();
1625
+ const canRefreshNow = !!latestRefreshToken && !isExpired(latestRefreshTokenExpiresAt);
1626
+ if (!canRefreshNow || !latestRefreshToken) {
1627
+ if (resolved.scope === 'application' && resolved.applicationCode) {
1628
+ auth.clearAppSession(resolved.applicationCode);
1629
+ }
1630
+ else {
1631
+ auth.logout();
1632
+ }
1633
+ return throwError(() => error);
1212
1634
  }
1213
- return throwError(() => error);
1635
+ return refreshTokens$(buildRefreshContext(), latestRefreshToken).pipe(catchError$1((refreshError) => {
1636
+ if (resolved.scope === 'application' && resolved.applicationCode) {
1637
+ auth.clearAppSession(resolved.applicationCode);
1638
+ }
1639
+ else {
1640
+ auth.logout();
1641
+ }
1642
+ return throwError(() => refreshError);
1643
+ }), switchMap((tokens) => next(prepareRequest(req, tokens.accessToken, true, baseUrl))));
1214
1644
  }));
1215
1645
  };
1216
1646
 
@@ -1415,9 +1845,11 @@ class GatewayLoginPage {
1415
1845
  return;
1416
1846
  }
1417
1847
  const { username, password } = this.loginForm.getRawValue();
1848
+ const applicationCode = resolveApplicationCodeOption(this.options.applicationCode);
1418
1849
  this.authFacade.login({
1419
1850
  userName: username,
1420
1851
  password,
1852
+ applicationCode: applicationCode ?? undefined,
1421
1853
  isEncrypted: false,
1422
1854
  deviceToken: resolveGatewayDeviceToken(this.options.deviceToken),
1423
1855
  });
@@ -1557,5 +1989,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1557
1989
  * Generated bundle index. Do not edit.
1558
1990
  */
1559
1991
 
1560
- export { AUTH_STATE_DEFAULTS, ClearError, ClearPendingMfa, ClearRateLimit, ExchangeSsoCode, GATEWAY_AUTH_ACCESS_TOKEN_REFRESH_SKEW_MS, GATEWAY_AUTH_DEVICE_TOKEN, GATEWAY_AUTH_DEVICE_TOKEN_STORAGE_KEY, GATEWAY_AUTH_ENDPOINTS, GATEWAY_AUTH_NGSW_BYPASS_PARAM, GATEWAY_AUTH_OPTIONS, GATEWAY_AUTH_RETRY_CONTEXT, GATEWAY_RATE_LIMIT_ERROR_CODE, GATEWAY_RATE_LIMIT_STATUS, GatewayAuthFacade, GatewayAuthState, GatewayLoginPage, GatewayMfa, GatewaySsoButtons, GatewaySsoCallback, GatewaySsoSession, LoadSsoProviders, Login, LoginFailure, LoginSuccess, Logout, ResendMfa, SetRateLimit, StartSso, UpdateTokens, UpdateUserData, VerifyMfa, buildGatewayUrl, buildSsoStartUrl, createSecureClientState, extractGatewayRateLimitInfo, gatewayAuthInterceptor, getGatewayErrorMessage, hasGatewayTokens, isExpired, isGatewayAuthRequestUrl, mapGatewayTokens, mapGatewayUser, normalizeGatewayBase, readPersistedGatewayAuthTokens, resolveAccessTokenRefreshSkewMs, resolveApiDateValue, resolveGatewayAuthPath, resolveGatewayDeviceToken, sanitizePersistedAuthState, withGatewayAuthNgswBypass };
1992
+ export { AUTH_STATE_DEFAULTS, ClearAllAppSessions, ClearAppSession, ClearError, ClearPendingMfa, ClearRateLimit, ExchangeSsoCode, GATEWAY_AUTH_ACCESS_TOKEN_REFRESH_SKEW_MS, GATEWAY_AUTH_DEVICE_TOKEN, GATEWAY_AUTH_DEVICE_TOKEN_STORAGE_KEY, GATEWAY_AUTH_ENDPOINTS, GATEWAY_AUTH_NGSW_BYPASS_PARAM, GATEWAY_AUTH_OPTIONS, GATEWAY_AUTH_RETRY_CONTEXT, GATEWAY_RATE_LIMIT_ERROR_CODE, GATEWAY_RATE_LIMIT_STATUS, GatewayAuthFacade, GatewayAuthState, GatewayLoginPage, GatewayMfa, GatewaySsoButtons, GatewaySsoCallback, GatewaySsoSession, LaunchApplication, LoadApplications, LoadSsoProviders, Login, LoginFailure, LoginSuccess, Logout, ResendMfa, SetAppSession, SetApplications, SetRateLimit, StartSso, UpdateAppTokens, UpdateTokens, UpdateUserData, VerifyMfa, buildGatewayUrl, buildSsoStartUrl, createSecureClientState, extractGatewayRateLimitInfo, gatewayAuthInterceptor, getGatewayErrorMessage, hasGatewayTokens, isExpired, isGatewayAuthRequestUrl, mapGatewayTokens, mapGatewayUser, normalizeGatewayBase, readPersistedGatewayAuthTokens, resolveAccessTokenRefreshSkewMs, resolveApiDateValue, resolveApplicationCodeOption, resolveGatewayAuthPath, resolveGatewayDeviceToken, sanitizePersistedAuthState, withGatewayAuthNgswBypass };
1561
1993
  //# sourceMappingURL=masterteam-gateway-auth.mjs.map