@masterteam/gateway-auth 0.0.16 → 0.0.18

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,11 +1,11 @@
1
+ import { firstValueFrom, EMPTY, of, defer, from, isObservable, map, tap as tap$1, shareReplay, finalize, catchError as catchError$1, throwError, switchMap as switchMap$1 } from 'rxjs';
1
2
  import { HttpClient, HttpContextToken, HttpBackend } from '@angular/common/http';
2
3
  import * as i0 from '@angular/core';
3
4
  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';
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';
8
- import { mergeMap, catchError, tap } from 'rxjs/operators';
8
+ import { switchMap, mergeMap, catchError, tap } from 'rxjs/operators';
9
9
  import * as i2 from '@angular/common';
10
10
  import { NgTemplateOutlet, CommonModule } from '@angular/common';
11
11
  import * as i1 from '@angular/forms';
@@ -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,53 @@ 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
+ }
69
+ const applicationContextCache = new Map();
70
+ function buildApplicationContextUrl(applicationApiBaseUrl) {
71
+ if (!applicationApiBaseUrl) {
72
+ return null;
73
+ }
74
+ const base = normalizeGatewayBase(applicationApiBaseUrl);
75
+ if (!base) {
76
+ return null;
77
+ }
78
+ const path = GATEWAY_AUTH_ENDPOINTS.applicationContext;
79
+ if (/\/api\/?$/i.test(base)) {
80
+ return `${base.replace(/\/+$/, '')}/${path}`;
81
+ }
82
+ return `${base}/api/${path}`;
83
+ }
84
+ function clearApplicationContextCache(applicationApiBaseUrl) {
85
+ if (applicationApiBaseUrl) {
86
+ applicationContextCache.delete(applicationApiBaseUrl);
87
+ return;
88
+ }
89
+ applicationContextCache.clear();
90
+ }
91
+ async function fetchApplicationContextCode(http, applicationApiBaseUrl) {
92
+ const url = buildApplicationContextUrl(applicationApiBaseUrl);
93
+ if (!url || !applicationApiBaseUrl) {
94
+ return null;
95
+ }
96
+ const cacheKey = applicationApiBaseUrl;
97
+ const cached = applicationContextCache.get(cacheKey);
98
+ if (cached) {
99
+ return cached;
100
+ }
101
+ const promise = firstValueFrom(http.get(url))
102
+ .then((response) => response?.data?.applicationCode?.trim() || null)
103
+ .catch(() => null);
104
+ applicationContextCache.set(cacheKey, promise);
105
+ const code = await promise;
106
+ if (!code) {
107
+ applicationContextCache.delete(cacheKey);
108
+ }
109
+ return code;
110
+ }
61
111
  function resolveGatewayDeviceToken(deviceToken) {
62
112
  const configuredToken = typeof deviceToken === 'function' ? deviceToken() : deviceToken;
63
113
  const normalizedConfiguredToken = configuredToken?.trim();
@@ -310,6 +360,53 @@ class SetRateLimit {
310
360
  class ClearRateLimit {
311
361
  static type = '[Auth] Clear Rate Limit';
312
362
  }
363
+ class LoadApplications {
364
+ static type = '[Auth] Load Applications';
365
+ }
366
+ class SetApplications {
367
+ applications;
368
+ static type = '[Auth] Set Applications';
369
+ constructor(applications) {
370
+ this.applications = applications;
371
+ }
372
+ }
373
+ class LaunchApplication {
374
+ applicationCode;
375
+ returnUrl;
376
+ navigate;
377
+ static type = '[Auth] Launch Application';
378
+ constructor(applicationCode, returnUrl, navigate = false) {
379
+ this.applicationCode = applicationCode;
380
+ this.returnUrl = returnUrl;
381
+ this.navigate = navigate;
382
+ }
383
+ }
384
+ class SetAppSession {
385
+ session;
386
+ static type = '[Auth] Set App Session';
387
+ constructor(session) {
388
+ this.session = session;
389
+ }
390
+ }
391
+ class UpdateAppTokens {
392
+ applicationCode;
393
+ tokens;
394
+ static type = '[Auth] Update App Tokens';
395
+ constructor(applicationCode, tokens) {
396
+ this.applicationCode = applicationCode;
397
+ this.tokens = tokens;
398
+ }
399
+ }
400
+ class ClearAppSession {
401
+ applicationCode;
402
+ static type = '[Auth] Clear App Session';
403
+ constructor(applicationCode) {
404
+ this.applicationCode = applicationCode;
405
+ }
406
+ }
407
+ class ClearAllAppSessions {
408
+ static type = '[Auth] Clear All App Sessions';
409
+ }
313
410
 
314
411
  const GATEWAY_AUTH_OPTIONS = new InjectionToken('GATEWAY_AUTH_OPTIONS', {
315
412
  factory: () => ({
@@ -321,6 +418,7 @@ const GATEWAY_AUTH_OPTIONS = new InjectionToken('GATEWAY_AUTH_OPTIONS', {
321
418
  ssoCallbackPath: '/auth/sso/callback',
322
419
  defaultAuthenticatedRoute: '/',
323
420
  preserveSsoProvidersOnLogout: true,
421
+ autoLaunchApplicationOnLogin: false,
324
422
  loginPage: {
325
423
  imageBasePath: '/Settings/login-image/',
326
424
  translationPrefix: 'login',
@@ -378,6 +476,10 @@ const AUTH_STATE_DEFAULTS = {
378
476
  pendingMfa: null,
379
477
  ssoProviders: [],
380
478
  rateLimit: null,
479
+ applications: [],
480
+ applicationsLoading: false,
481
+ appSessions: {},
482
+ appLaunchLoading: {},
381
483
  };
382
484
  function pruneRateLimit(rateLimit) {
383
485
  if (!rateLimit) {
@@ -389,6 +491,22 @@ function pruneRateLimit(rateLimit) {
389
491
  }
390
492
  return rateLimit;
391
493
  }
494
+ function pruneAppSessions(appSessions) {
495
+ if (!appSessions || typeof appSessions !== 'object') {
496
+ return {};
497
+ }
498
+ const pruned = {};
499
+ for (const [code, session] of Object.entries(appSessions)) {
500
+ if (!session || typeof session !== 'object') {
501
+ continue;
502
+ }
503
+ if (!session.accessToken || !session.refreshToken) {
504
+ continue;
505
+ }
506
+ pruned[code] = session;
507
+ }
508
+ return pruned;
509
+ }
392
510
  function sanitizePersistedAuthState(obj) {
393
511
  return {
394
512
  ...AUTH_STATE_DEFAULTS,
@@ -402,6 +520,10 @@ function sanitizePersistedAuthState(obj) {
402
520
  pendingMfa: null,
403
521
  ssoProviders: [],
404
522
  rateLimit: pruneRateLimit(obj?.rateLimit ?? null),
523
+ applications: obj?.applications ?? [],
524
+ applicationsLoading: false,
525
+ appSessions: pruneAppSessions(obj?.appSessions ?? null),
526
+ appLaunchLoading: {},
405
527
  };
406
528
  }
407
529
 
@@ -465,6 +587,18 @@ let GatewayAuthState = class GatewayAuthState {
465
587
  static userDetails(state) {
466
588
  return state.user?.user || null;
467
589
  }
590
+ static applications(state) {
591
+ return state.applications;
592
+ }
593
+ static applicationsLoading(state) {
594
+ return state.applicationsLoading;
595
+ }
596
+ static appSessions(state) {
597
+ return state.appSessions;
598
+ }
599
+ static appLaunchLoading(state) {
600
+ return state.appLaunchLoading;
601
+ }
468
602
  login(ctx, action) {
469
603
  if (this.isRateLimitActive(ctx, 'login')) {
470
604
  return EMPTY;
@@ -475,22 +609,37 @@ let GatewayAuthState = class GatewayAuthState {
475
609
  pendingMfa: null,
476
610
  twoFactorRequired: false,
477
611
  });
478
- return this.http
612
+ return this.resolveApplicationCode$(action.payload.applicationCode).pipe(switchMap((applicationCode) => this.http
479
613
  .post(this.gatewayAuthMutationUrl(GATEWAY_AUTH_ENDPOINTS.login), {
480
614
  userName: action.payload.userName,
481
615
  password: action.payload.password,
616
+ applicationCode: applicationCode ?? undefined,
482
617
  isEncrypted: action.payload.isEncrypted ?? false,
483
618
  deviceToken: action.payload.deviceToken || this.deviceToken,
484
619
  recaptchaToken: action.payload.recaptchaToken,
485
620
  recaptchaAction: action.payload.recaptchaAction,
486
621
  })
487
- .pipe(mergeMap((response) => this.handleLoginResponse(ctx, response.data)), catchError((error) => {
622
+ .pipe(mergeMap((response) => this.handleLoginResponse(ctx, response.data, applicationCode)), catchError((error) => {
488
623
  if (this.handleRateLimit(ctx, error, 'login')) {
489
624
  return of(null);
490
625
  }
491
626
  ctx.dispatch(new LoginFailure(getGatewayErrorMessage(error, 'Login failed')));
492
627
  return of(null);
493
- }));
628
+ }))));
629
+ }
630
+ resolveApplicationCode$(explicitCode) {
631
+ if (explicitCode) {
632
+ return of(explicitCode);
633
+ }
634
+ const configured = resolveApplicationCodeOption(this.options.applicationCode);
635
+ if (configured) {
636
+ return of(configured);
637
+ }
638
+ const applicationApiBaseUrl = this.options.getApplicationApiBaseUrl?.();
639
+ if (!applicationApiBaseUrl) {
640
+ return of(null);
641
+ }
642
+ return defer(() => from(fetchApplicationContextCode(this.http, applicationApiBaseUrl)));
494
643
  }
495
644
  verifyMfa(ctx, action) {
496
645
  const pendingMfa = ctx.getState().pendingMfa;
@@ -637,6 +786,9 @@ let GatewayAuthState = class GatewayAuthState {
637
786
  refreshTokenExpiresAt: null,
638
787
  pendingMfa: null,
639
788
  twoFactorRequired: false,
789
+ applications: [],
790
+ appSessions: {},
791
+ appLaunchLoading: {},
640
792
  });
641
793
  }
642
794
  logout(ctx, action) {
@@ -710,6 +862,125 @@ let GatewayAuthState = class GatewayAuthState {
710
862
  clearRateLimit(ctx) {
711
863
  ctx.patchState({ rateLimit: null });
712
864
  }
865
+ loadApplications(ctx) {
866
+ ctx.patchState({ applicationsLoading: true });
867
+ return this.http
868
+ .get(this.gatewayUrl(GATEWAY_AUTH_ENDPOINTS.meApplications))
869
+ .pipe(tap((response) => {
870
+ ctx.patchState({
871
+ applications: response.data?.applications ?? [],
872
+ applicationsLoading: false,
873
+ });
874
+ }), catchError((error) => {
875
+ ctx.patchState({
876
+ applicationsLoading: false,
877
+ error: getGatewayErrorMessage(error, 'Failed to load applications'),
878
+ });
879
+ return of(null);
880
+ }));
881
+ }
882
+ setApplications(ctx, action) {
883
+ ctx.patchState({ applications: action.applications });
884
+ }
885
+ launchApplication(ctx, action) {
886
+ const code = action.applicationCode;
887
+ ctx.patchState({
888
+ appLaunchLoading: {
889
+ ...ctx.getState().appLaunchLoading,
890
+ [code]: true,
891
+ },
892
+ });
893
+ const params = {};
894
+ if (action.returnUrl) {
895
+ params['returnUrl'] = action.returnUrl;
896
+ }
897
+ const deviceToken = this.deviceToken;
898
+ if (deviceToken) {
899
+ params['deviceToken'] = deviceToken;
900
+ }
901
+ return this.http
902
+ .get(this.gatewayUrl(GATEWAY_AUTH_ENDPOINTS.applicationLaunch(code)), { params })
903
+ .pipe(tap((response) => {
904
+ const data = response.data;
905
+ if (!data || !hasGatewayTokens(data.tokens)) {
906
+ ctx.patchState({
907
+ appLaunchLoading: this.removeLoadingFlag(ctx, code),
908
+ });
909
+ return;
910
+ }
911
+ const mapped = mapGatewayTokens(data.tokens);
912
+ const session = {
913
+ applicationCode: data.applicationCode,
914
+ applicationName: data.applicationName,
915
+ launchUrl: data.launchUrl,
916
+ accessToken: mapped.accessToken,
917
+ refreshToken: mapped.refreshToken,
918
+ accessTokenExpiresAt: mapped.accessTokenExpiresAt,
919
+ refreshTokenExpiresAt: mapped.refreshTokenExpiresAt,
920
+ };
921
+ ctx.patchState({
922
+ appSessions: {
923
+ ...ctx.getState().appSessions,
924
+ [data.applicationCode]: session,
925
+ },
926
+ appLaunchLoading: this.removeLoadingFlag(ctx, code),
927
+ });
928
+ if (action.navigate && session.launchUrl) {
929
+ try {
930
+ window.location.assign(session.launchUrl);
931
+ }
932
+ catch {
933
+ // ignore navigation errors in non-browser env
934
+ }
935
+ }
936
+ }), catchError((error) => {
937
+ ctx.patchState({
938
+ appLaunchLoading: this.removeLoadingFlag(ctx, code),
939
+ error: getGatewayErrorMessage(error, 'Failed to launch application'),
940
+ });
941
+ return of(null);
942
+ }));
943
+ }
944
+ setAppSession(ctx, action) {
945
+ ctx.patchState({
946
+ appSessions: {
947
+ ...ctx.getState().appSessions,
948
+ [action.session.applicationCode]: action.session,
949
+ },
950
+ });
951
+ }
952
+ updateAppTokens(ctx, action) {
953
+ const existing = ctx.getState().appSessions[action.applicationCode];
954
+ if (!existing) {
955
+ return;
956
+ }
957
+ const mapped = mapGatewayTokens(action.tokens);
958
+ ctx.patchState({
959
+ appSessions: {
960
+ ...ctx.getState().appSessions,
961
+ [action.applicationCode]: {
962
+ ...existing,
963
+ accessToken: mapped.accessToken,
964
+ refreshToken: mapped.refreshToken,
965
+ accessTokenExpiresAt: mapped.accessTokenExpiresAt,
966
+ refreshTokenExpiresAt: mapped.refreshTokenExpiresAt,
967
+ },
968
+ },
969
+ });
970
+ }
971
+ clearAppSession(ctx, action) {
972
+ const sessions = { ...ctx.getState().appSessions };
973
+ delete sessions[action.applicationCode];
974
+ ctx.patchState({ appSessions: sessions });
975
+ }
976
+ clearAllAppSessions(ctx) {
977
+ ctx.patchState({ appSessions: {}, applications: [] });
978
+ }
979
+ removeLoadingFlag(ctx, code) {
980
+ const next = { ...ctx.getState().appLaunchLoading };
981
+ delete next[code];
982
+ return next;
983
+ }
713
984
  isRateLimitActive(ctx, scope) {
714
985
  const lock = ctx.getState().rateLimit;
715
986
  if (!lock || lock.scope !== scope) {
@@ -737,7 +1008,7 @@ let GatewayAuthState = class GatewayAuthState {
737
1008
  }));
738
1009
  return true;
739
1010
  }
740
- handleLoginResponse(ctx, session) {
1011
+ handleLoginResponse(ctx, session, resolvedApplicationCode) {
741
1012
  if (session.requiresTwoFactor) {
742
1013
  ctx.patchState({
743
1014
  user: null,
@@ -773,13 +1044,26 @@ let GatewayAuthState = class GatewayAuthState {
773
1044
  error: null,
774
1045
  pendingMfa: null,
775
1046
  twoFactorRequired: false,
1047
+ appSessions: {},
1048
+ appLaunchLoading: {},
776
1049
  });
777
- return this.toObservable(this.options.afterLogin?.(session, ctx)).pipe(tap(() => {
1050
+ return this.toObservable(this.options.afterLogin?.(session, ctx)).pipe(mergeMap(() => this.maybeAutoLaunchApplication(ctx, resolvedApplicationCode)), tap(() => {
778
1051
  this.router.navigateByUrl(this.resolveAuthenticatedRoute(), {
779
1052
  replaceUrl: true,
780
1053
  });
781
1054
  }));
782
1055
  }
1056
+ maybeAutoLaunchApplication(ctx, resolvedApplicationCode) {
1057
+ if (!this.options.autoLaunchApplicationOnLogin) {
1058
+ return of(null);
1059
+ }
1060
+ const code = resolvedApplicationCode ??
1061
+ resolveApplicationCodeOption(this.options.applicationCode);
1062
+ if (!code) {
1063
+ return of(null);
1064
+ }
1065
+ return ctx.dispatch(new LaunchApplication(code));
1066
+ }
783
1067
  get deviceToken() {
784
1068
  return resolveGatewayDeviceToken(this.options.deviceToken);
785
1069
  }
@@ -874,6 +1158,27 @@ __decorate([
874
1158
  __decorate([
875
1159
  Action(ClearRateLimit)
876
1160
  ], GatewayAuthState.prototype, "clearRateLimit", null);
1161
+ __decorate([
1162
+ Action(LoadApplications)
1163
+ ], GatewayAuthState.prototype, "loadApplications", null);
1164
+ __decorate([
1165
+ Action(SetApplications)
1166
+ ], GatewayAuthState.prototype, "setApplications", null);
1167
+ __decorate([
1168
+ Action(LaunchApplication)
1169
+ ], GatewayAuthState.prototype, "launchApplication", null);
1170
+ __decorate([
1171
+ Action(SetAppSession)
1172
+ ], GatewayAuthState.prototype, "setAppSession", null);
1173
+ __decorate([
1174
+ Action(UpdateAppTokens)
1175
+ ], GatewayAuthState.prototype, "updateAppTokens", null);
1176
+ __decorate([
1177
+ Action(ClearAppSession)
1178
+ ], GatewayAuthState.prototype, "clearAppSession", null);
1179
+ __decorate([
1180
+ Action(ClearAllAppSessions)
1181
+ ], GatewayAuthState.prototype, "clearAllAppSessions", null);
877
1182
  __decorate([
878
1183
  Selector()
879
1184
  ], GatewayAuthState, "user", null);
@@ -922,6 +1227,18 @@ __decorate([
922
1227
  __decorate([
923
1228
  Selector()
924
1229
  ], GatewayAuthState, "userDetails", null);
1230
+ __decorate([
1231
+ Selector()
1232
+ ], GatewayAuthState, "applications", null);
1233
+ __decorate([
1234
+ Selector()
1235
+ ], GatewayAuthState, "applicationsLoading", null);
1236
+ __decorate([
1237
+ Selector()
1238
+ ], GatewayAuthState, "appSessions", null);
1239
+ __decorate([
1240
+ Selector()
1241
+ ], GatewayAuthState, "appLaunchLoading", null);
925
1242
  GatewayAuthState = __decorate([
926
1243
  State({
927
1244
  name: 'auth',
@@ -930,7 +1247,7 @@ GatewayAuthState = __decorate([
930
1247
  ], GatewayAuthState);
931
1248
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GatewayAuthState, decorators: [{
932
1249
  type: Injectable
933
- }], propDecorators: { login: [], verifyMfa: [], resendMfa: [], loadSsoProviders: [], startSso: [], exchangeSsoCode: [], loginSuccess: [], loginFailure: [], logout: [], updateUserData: [], updateTokens: [], clearError: [], clearPendingMfa: [], setRateLimit: [], clearRateLimit: [] } });
1250
+ }], propDecorators: { login: [], verifyMfa: [], resendMfa: [], loadSsoProviders: [], startSso: [], exchangeSsoCode: [], loginSuccess: [], loginFailure: [], logout: [], updateUserData: [], updateTokens: [], clearError: [], clearPendingMfa: [], setRateLimit: [], clearRateLimit: [], loadApplications: [], setApplications: [], launchApplication: [], setAppSession: [], updateAppTokens: [], clearAppSession: [], clearAllAppSessions: [] } });
934
1251
 
935
1252
  class GatewayAuthFacade {
936
1253
  store = inject(Store);
@@ -950,6 +1267,10 @@ class GatewayAuthFacade {
950
1267
  rateLimit = select(GatewayAuthState.rateLimit);
951
1268
  isAdmin = select(GatewayAuthState.isAdmin);
952
1269
  userDetails = select(GatewayAuthState.userDetails);
1270
+ applications = select(GatewayAuthState.applications);
1271
+ applicationsLoading = select(GatewayAuthState.applicationsLoading);
1272
+ appSessions = select(GatewayAuthState.appSessions);
1273
+ appLaunchLoading = select(GatewayAuthState.appLaunchLoading);
953
1274
  hasError = computed(() => this.error() !== null, ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
954
1275
  isReady = computed(() => !this.loading() && this.error() === null, ...(ngDevMode ? [{ debugName: "isReady" }] : /* istanbul ignore next */ []));
955
1276
  userDisplayName = computed(() => this.user()?.user?.displayName || '', ...(ngDevMode ? [{ debugName: "userDisplayName" }] : /* istanbul ignore next */ []));
@@ -993,6 +1314,34 @@ class GatewayAuthFacade {
993
1314
  updateTokens(tokens) {
994
1315
  this.store.dispatch(new UpdateTokens(tokens));
995
1316
  }
1317
+ loadApplications() {
1318
+ this.store.dispatch(new LoadApplications());
1319
+ }
1320
+ setApplications(applications) {
1321
+ this.store.dispatch(new SetApplications(applications));
1322
+ }
1323
+ launchApplication(applicationCode, returnUrl, navigate = false) {
1324
+ this.store.dispatch(new LaunchApplication(applicationCode, returnUrl, navigate));
1325
+ }
1326
+ setAppSession(session) {
1327
+ this.store.dispatch(new SetAppSession(session));
1328
+ }
1329
+ updateAppTokens(applicationCode, tokens) {
1330
+ this.store.dispatch(new UpdateAppTokens(applicationCode, tokens));
1331
+ }
1332
+ clearAppSession(applicationCode) {
1333
+ this.store.dispatch(new ClearAppSession(applicationCode));
1334
+ }
1335
+ clearAllAppSessions() {
1336
+ this.store.dispatch(new ClearAllAppSessions());
1337
+ }
1338
+ getAppSession(applicationCode) {
1339
+ return this.appSessions()?.[applicationCode] ?? null;
1340
+ }
1341
+ getAppToken(applicationCode) {
1342
+ const session = this.getAppSession(applicationCode);
1343
+ return session?.accessToken ?? null;
1344
+ }
996
1345
  clearError() {
997
1346
  this.store.dispatch(new ClearError());
998
1347
  }
@@ -1064,7 +1413,6 @@ function isGatewayAuthRequestUrl(url, gatewayApiBaseUrl) {
1064
1413
  return (GATEWAY_AUTH_ENDPOINT_PATHS.has(path) ||
1065
1414
  GATEWAY_AUTH_ENDPOINT_PREFIXES.some((prefix) => path.startsWith(prefix)));
1066
1415
  }
1067
- let inflightRefresh$ = null;
1068
1416
  const getBrowserRefreshLock = () => {
1069
1417
  if (typeof navigator === 'undefined') {
1070
1418
  return null;
@@ -1072,12 +1420,12 @@ const getBrowserRefreshLock = () => {
1072
1420
  const locks = navigator.locks;
1073
1421
  return typeof locks?.request === 'function' ? locks : null;
1074
1422
  };
1075
- const runWithRefreshLock = (operation) => {
1423
+ const runWithRefreshLock = (scopeKey, operation) => {
1076
1424
  const locks = getBrowserRefreshLock();
1077
- return locks
1078
- ? locks.request('masterteam-gateway-auth-refresh', operation)
1079
- : operation();
1425
+ return locks ? locks.request(scopeKey, operation) : operation();
1080
1426
  };
1427
+ const GATEWAY_REFRESH_SCOPE = 'gateway';
1428
+ const inflightRefreshByScope = new Map();
1081
1429
  const getCurrentAuthTokens = (auth) => {
1082
1430
  const accessToken = auth.token();
1083
1431
  const refreshToken = auth.refreshToken();
@@ -1099,7 +1447,7 @@ const tokenHasUsableAccessToken = (tokens, refreshSkewMs) => !!tokens.accessToke
1099
1447
  !isExpired(resolveApiDateValue(tokens.accessTokenExpiresAt), refreshSkewMs);
1100
1448
  const tokenHasUsableRefreshToken = (tokens) => !!tokens.refreshToken &&
1101
1449
  !isExpired(resolveApiDateValue(tokens.refreshTokenExpiresAt));
1102
- const getSynchronizedTokens = (auth, staleRefreshToken, refreshSkewMs) => {
1450
+ const getSynchronizedGatewayTokens = (auth, staleRefreshToken, refreshSkewMs) => {
1103
1451
  const candidates = [
1104
1452
  getCurrentAuthTokens(auth),
1105
1453
  readPersistedGatewayAuthTokens(),
@@ -1108,7 +1456,7 @@ const getSynchronizedTokens = (auth, staleRefreshToken, refreshSkewMs) => {
1108
1456
  tokens.refreshToken !== staleRefreshToken &&
1109
1457
  tokenHasUsableAccessToken(tokens, refreshSkewMs)) ?? null);
1110
1458
  };
1111
- const getLatestRefreshToken = (auth, fallbackRefreshToken) => {
1459
+ const getLatestGatewayRefreshToken = (auth, fallbackRefreshToken) => {
1112
1460
  const candidates = [
1113
1461
  getCurrentAuthTokens(auth),
1114
1462
  readPersistedGatewayAuthTokens(),
@@ -1118,33 +1466,95 @@ const getLatestRefreshToken = (auth, fallbackRefreshToken) => {
1118
1466
  tokenHasUsableRefreshToken(tokens));
1119
1467
  return latestTokens?.refreshToken ?? fallbackRefreshToken;
1120
1468
  };
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))));
1469
+ const sessionAsTokens = (session) => ({
1470
+ accessToken: session.accessToken,
1471
+ accessTokenExpiresAt: { actualValue: session.accessTokenExpiresAt ?? '' },
1472
+ refreshToken: session.refreshToken,
1473
+ refreshTokenExpiresAt: { actualValue: session.refreshTokenExpiresAt ?? '' },
1140
1474
  });
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
- }));
1475
+ const getSynchronizedAppTokens = (auth, applicationCode, staleRefreshToken, refreshSkewMs) => {
1476
+ const session = auth.getAppSession(applicationCode);
1477
+ if (!session) {
1478
+ return null;
1479
+ }
1480
+ const tokens = sessionAsTokens(session);
1481
+ if (tokens.refreshToken !== staleRefreshToken &&
1482
+ tokenHasUsableAccessToken(tokens, refreshSkewMs)) {
1483
+ return tokens;
1146
1484
  }
1147
- return inflightRefresh$;
1485
+ return null;
1486
+ };
1487
+ const getLatestAppRefreshToken = (auth, applicationCode, fallbackRefreshToken) => {
1488
+ const session = auth.getAppSession(applicationCode);
1489
+ if (!session) {
1490
+ return fallbackRefreshToken;
1491
+ }
1492
+ if (session.refreshToken &&
1493
+ session.refreshToken !== fallbackRefreshToken &&
1494
+ !isExpired(session.refreshTokenExpiresAt)) {
1495
+ return session.refreshToken;
1496
+ }
1497
+ return fallbackRefreshToken;
1498
+ };
1499
+ const executeRefresh = (context, refreshToken) => {
1500
+ const { http, gatewayApiBaseUrl, auth, deviceToken, refreshSkewMs, scope, applicationCode, } = context;
1501
+ const lockKey = scope === 'gateway'
1502
+ ? 'masterteam-gateway-auth-refresh:gateway'
1503
+ : `masterteam-gateway-auth-refresh:app:${applicationCode ?? ''}`;
1504
+ return runWithRefreshLock(lockKey, async () => {
1505
+ if (scope === 'gateway') {
1506
+ const synchronizedTokens = getSynchronizedGatewayTokens(auth, refreshToken, refreshSkewMs);
1507
+ if (synchronizedTokens) {
1508
+ auth.updateTokens(synchronizedTokens);
1509
+ return synchronizedTokens;
1510
+ }
1511
+ }
1512
+ else if (applicationCode) {
1513
+ const synchronizedTokens = getSynchronizedAppTokens(auth, applicationCode, refreshToken, refreshSkewMs);
1514
+ if (synchronizedTokens) {
1515
+ auth.updateAppTokens(applicationCode, synchronizedTokens);
1516
+ return synchronizedTokens;
1517
+ }
1518
+ }
1519
+ const latestRefreshToken = scope === 'gateway'
1520
+ ? getLatestGatewayRefreshToken(auth, refreshToken)
1521
+ : applicationCode
1522
+ ? getLatestAppRefreshToken(auth, applicationCode, refreshToken)
1523
+ : refreshToken;
1524
+ const url = buildGatewayUrl(gatewayApiBaseUrl, GATEWAY_AUTH_ENDPOINTS.refresh);
1525
+ return firstValueFrom(http
1526
+ .post(url, {
1527
+ refreshToken: latestRefreshToken,
1528
+ deviceToken,
1529
+ })
1530
+ .pipe(map((response) => response.data), map((tokens) => {
1531
+ if (!tokens?.accessToken || !tokens.refreshToken) {
1532
+ throw new Error('Invalid refresh response');
1533
+ }
1534
+ return tokens;
1535
+ }), tap$1((tokens) => {
1536
+ if (scope === 'gateway') {
1537
+ auth.updateTokens(tokens);
1538
+ }
1539
+ else if (applicationCode) {
1540
+ auth.updateAppTokens(applicationCode, tokens);
1541
+ }
1542
+ })));
1543
+ });
1544
+ };
1545
+ const refreshTokens$ = (context, refreshToken) => {
1546
+ const scopeKey = context.scope === 'gateway'
1547
+ ? GATEWAY_REFRESH_SCOPE
1548
+ : `application:${context.applicationCode ?? ''}`;
1549
+ const existing = inflightRefreshByScope.get(scopeKey);
1550
+ if (existing) {
1551
+ return existing;
1552
+ }
1553
+ const observable = from(executeRefresh(context, refreshToken)).pipe(shareReplay({ bufferSize: 1, refCount: true }), finalize(() => {
1554
+ inflightRefreshByScope.delete(scopeKey);
1555
+ }));
1556
+ inflightRefreshByScope.set(scopeKey, observable);
1557
+ return observable;
1148
1558
  };
1149
1559
  const prepareRequest = (req, token, markRetried, baseUrl) => {
1150
1560
  let modifiedReq = req;
@@ -1167,6 +1577,35 @@ const prepareRequest = (req, token, markRetried, baseUrl) => {
1167
1577
  }
1168
1578
  return modifiedReq;
1169
1579
  };
1580
+ const resolveRequestScope = (req, options, auth) => {
1581
+ const useGatewayBaseUrl = options.shouldUseGatewayApiBaseUrl?.(req) ?? false;
1582
+ const explicitCode = options.resolveApplicationCodeForRequest?.(req);
1583
+ if (explicitCode) {
1584
+ const session = auth.getAppSession(explicitCode);
1585
+ return {
1586
+ scope: 'application',
1587
+ applicationCode: explicitCode,
1588
+ session,
1589
+ useGatewayBaseUrl,
1590
+ };
1591
+ }
1592
+ if (useGatewayBaseUrl) {
1593
+ return { scope: 'gateway', useGatewayBaseUrl: true };
1594
+ }
1595
+ const defaultCode = resolveApplicationCodeOption(options.applicationCode);
1596
+ if (defaultCode) {
1597
+ const session = auth.getAppSession(defaultCode);
1598
+ if (session) {
1599
+ return {
1600
+ scope: 'application',
1601
+ applicationCode: defaultCode,
1602
+ session,
1603
+ useGatewayBaseUrl,
1604
+ };
1605
+ }
1606
+ }
1607
+ return { scope: 'gateway', useGatewayBaseUrl };
1608
+ };
1170
1609
  const gatewayAuthInterceptor = (req, next) => {
1171
1610
  if (isAssetRequest(req.url)) {
1172
1611
  return next(req);
@@ -1176,41 +1615,86 @@ const gatewayAuthInterceptor = (req, next) => {
1176
1615
  const http = new HttpClient(inject(HttpBackend));
1177
1616
  const gatewayApiBaseUrl = options.getGatewayApiBaseUrl();
1178
1617
  const appApiBaseUrl = options.getApplicationApiBaseUrl?.();
1179
- const useGatewayBaseUrl = options.shouldUseGatewayApiBaseUrl?.(req) ?? false;
1180
- const baseUrl = useGatewayBaseUrl ? gatewayApiBaseUrl : appApiBaseUrl;
1181
1618
  const deviceToken = resolveGatewayDeviceToken(options.deviceToken);
1182
1619
  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();
1620
+ const isAuthRequest = isGatewayAuthRequestUrl(req.url, gatewayApiBaseUrl);
1621
+ const alreadyRetried = req.context.get(GATEWAY_AUTH_RETRY_CONTEXT);
1622
+ const resolved = resolveRequestScope(req, options, auth);
1623
+ const baseUrl = resolved.useGatewayBaseUrl
1624
+ ? gatewayApiBaseUrl
1625
+ : appApiBaseUrl;
1626
+ const gatewayAccessToken = auth.token();
1627
+ const gatewayRefreshToken = auth.refreshToken();
1628
+ const gatewayAccessTokenExpiresAt = auth.accessTokenExpiresAt();
1629
+ const gatewayRefreshTokenExpiresAt = auth.refreshTokenExpiresAt();
1630
+ const session = resolved.session ?? null;
1631
+ const accessToken = resolved.scope === 'application' && session
1632
+ ? session.accessToken
1633
+ : gatewayAccessToken;
1634
+ const accessTokenExpiresAt = resolved.scope === 'application' && session
1635
+ ? session.accessTokenExpiresAt
1636
+ : gatewayAccessTokenExpiresAt;
1637
+ const refreshToken = resolved.scope === 'application' && session
1638
+ ? session.refreshToken
1639
+ : gatewayRefreshToken;
1640
+ const refreshTokenExpiresAt = resolved.scope === 'application' && session
1641
+ ? session.refreshTokenExpiresAt
1642
+ : gatewayRefreshTokenExpiresAt;
1187
1643
  const accessTokenValid = !!accessToken && !isExpired(accessTokenExpiresAt, refreshSkewMs);
1188
1644
  const canRefresh = !!refreshToken && !isExpired(refreshTokenExpiresAt);
1189
- const alreadyRetried = req.context.get(GATEWAY_AUTH_RETRY_CONTEXT);
1190
- const isAuthRequest = isGatewayAuthRequestUrl(req.url, gatewayApiBaseUrl);
1191
1645
  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
- }));
1646
+ const buildRefreshContext = (scopeOverride, appCodeOverride) => ({
1647
+ http,
1648
+ gatewayApiBaseUrl,
1649
+ auth,
1650
+ deviceToken,
1651
+ refreshSkewMs,
1652
+ scope: scopeOverride ?? resolved.scope,
1653
+ applicationCode: appCodeOverride ?? resolved.applicationCode,
1654
+ });
1655
+ if (!isAuthRequest && !accessTokenValid && canRefresh && refreshToken) {
1656
+ return refreshTokens$(buildRefreshContext(), refreshToken).pipe(catchError$1((refreshError) => {
1657
+ if (resolved.scope === 'application' && resolved.applicationCode) {
1658
+ auth.clearAppSession(resolved.applicationCode);
1659
+ }
1660
+ else {
1661
+ auth.logout();
1662
+ }
1663
+ return throwError(() => refreshError);
1664
+ }), switchMap$1((tokens) => next(prepareRequest(req, tokens.accessToken, true, baseUrl))));
1197
1665
  }
1198
1666
  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
- }
1667
+ if (error?.status !== 401 || isAuthRequest || alreadyRetried) {
1668
+ return throwError(() => error);
1209
1669
  }
1210
- if (error?.status === 401 && !isAuthRequest) {
1211
- auth.logout();
1670
+ const latestAppSession = resolved.scope === 'application' && resolved.applicationCode
1671
+ ? auth.getAppSession(resolved.applicationCode)
1672
+ : null;
1673
+ const latestRefreshToken = resolved.scope === 'application' && resolved.applicationCode
1674
+ ? (latestAppSession?.refreshToken ?? null)
1675
+ : auth.refreshToken();
1676
+ const latestRefreshTokenExpiresAt = resolved.scope === 'application' && resolved.applicationCode
1677
+ ? (latestAppSession?.refreshTokenExpiresAt ?? null)
1678
+ : auth.refreshTokenExpiresAt();
1679
+ const canRefreshNow = !!latestRefreshToken && !isExpired(latestRefreshTokenExpiresAt);
1680
+ if (!canRefreshNow || !latestRefreshToken) {
1681
+ if (resolved.scope === 'application' && resolved.applicationCode) {
1682
+ auth.clearAppSession(resolved.applicationCode);
1683
+ }
1684
+ else {
1685
+ auth.logout();
1686
+ }
1687
+ return throwError(() => error);
1212
1688
  }
1213
- return throwError(() => error);
1689
+ return refreshTokens$(buildRefreshContext(), latestRefreshToken).pipe(catchError$1((refreshError) => {
1690
+ if (resolved.scope === 'application' && resolved.applicationCode) {
1691
+ auth.clearAppSession(resolved.applicationCode);
1692
+ }
1693
+ else {
1694
+ auth.logout();
1695
+ }
1696
+ return throwError(() => refreshError);
1697
+ }), switchMap$1((tokens) => next(prepareRequest(req, tokens.accessToken, true, baseUrl))));
1214
1698
  }));
1215
1699
  };
1216
1700
 
@@ -1415,9 +1899,11 @@ class GatewayLoginPage {
1415
1899
  return;
1416
1900
  }
1417
1901
  const { username, password } = this.loginForm.getRawValue();
1902
+ const applicationCode = resolveApplicationCodeOption(this.options.applicationCode);
1418
1903
  this.authFacade.login({
1419
1904
  userName: username,
1420
1905
  password,
1906
+ applicationCode: applicationCode ?? undefined,
1421
1907
  isEncrypted: false,
1422
1908
  deviceToken: resolveGatewayDeviceToken(this.options.deviceToken),
1423
1909
  });
@@ -1557,5 +2043,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1557
2043
  * Generated bundle index. Do not edit.
1558
2044
  */
1559
2045
 
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 };
2046
+ 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, buildApplicationContextUrl, buildGatewayUrl, buildSsoStartUrl, clearApplicationContextCache, createSecureClientState, extractGatewayRateLimitInfo, fetchApplicationContextCode, gatewayAuthInterceptor, getGatewayErrorMessage, hasGatewayTokens, isExpired, isGatewayAuthRequestUrl, mapGatewayTokens, mapGatewayUser, normalizeGatewayBase, readPersistedGatewayAuthTokens, resolveAccessTokenRefreshSkewMs, resolveApiDateValue, resolveApplicationCodeOption, resolveGatewayAuthPath, resolveGatewayDeviceToken, sanitizePersistedAuthState, withGatewayAuthNgswBypass };
1561
2047
  //# sourceMappingURL=masterteam-gateway-auth.mjs.map