@masterteam/gateway-auth 0.0.12 → 0.0.13

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,9 +1,10 @@
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 { of, EMPTY, 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, 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
+ import { ModalService } from '@masterteam/components/modal';
7
8
  import { mergeMap, catchError, tap } from 'rxjs/operators';
8
9
  import * as i2 from '@angular/common';
9
10
  import { NgTemplateOutlet, CommonModule } from '@angular/common';
@@ -149,6 +150,25 @@ function mapGatewayUser(user, tokenData, delegations) {
149
150
  function hasGatewayTokens(tokens) {
150
151
  return Boolean(tokens?.accessToken && tokens?.refreshToken);
151
152
  }
153
+ const GATEWAY_RATE_LIMIT_STATUS = 429;
154
+ const GATEWAY_RATE_LIMIT_ERROR_CODE = 'RATE_001';
155
+ function extractGatewayRateLimitInfo(error) {
156
+ const e = error;
157
+ if (!e || e.status !== GATEWAY_RATE_LIMIT_STATUS) {
158
+ return null;
159
+ }
160
+ const detailSeconds = Number(e.error?.errors?.details?.retryAfterSeconds);
161
+ const headerSeconds = Number(e.headers?.get?.('Retry-After'));
162
+ const retryAfterSeconds = Number.isFinite(detailSeconds) && detailSeconds > 0
163
+ ? Math.ceil(detailSeconds)
164
+ : Number.isFinite(headerSeconds) && headerSeconds > 0
165
+ ? Math.ceil(headerSeconds)
166
+ : null;
167
+ const message = e.error?.errors?.message ??
168
+ e.error?.message ??
169
+ 'Too many attempts. Please try again later.';
170
+ return { retryAfterSeconds, message };
171
+ }
152
172
  function getGatewayErrorMessage(error, fallback) {
153
173
  const response = error;
154
174
  return (response.error?.message ??
@@ -280,6 +300,16 @@ class ClearError {
280
300
  class ClearPendingMfa {
281
301
  static type = '[Auth] Clear Pending MFA';
282
302
  }
303
+ class SetRateLimit {
304
+ lock;
305
+ static type = '[Auth] Set Rate Limit';
306
+ constructor(lock) {
307
+ this.lock = lock;
308
+ }
309
+ }
310
+ class ClearRateLimit {
311
+ static type = '[Auth] Clear Rate Limit';
312
+ }
283
313
 
284
314
  const GATEWAY_AUTH_OPTIONS = new InjectionToken('GATEWAY_AUTH_OPTIONS', {
285
315
  factory: () => ({
@@ -347,7 +377,18 @@ const AUTH_STATE_DEFAULTS = {
347
377
  twoFactorRequired: false,
348
378
  pendingMfa: null,
349
379
  ssoProviders: [],
380
+ rateLimit: null,
350
381
  };
382
+ function pruneRateLimit(rateLimit) {
383
+ if (!rateLimit) {
384
+ return null;
385
+ }
386
+ if (typeof rateLimit.retryUntilMs === 'number' &&
387
+ rateLimit.retryUntilMs <= Date.now()) {
388
+ return null;
389
+ }
390
+ return rateLimit;
391
+ }
351
392
  function sanitizePersistedAuthState(obj) {
352
393
  return {
353
394
  ...AUTH_STATE_DEFAULTS,
@@ -360,6 +401,7 @@ function sanitizePersistedAuthState(obj) {
360
401
  twoFactorRequired: false,
361
402
  pendingMfa: null,
362
403
  ssoProviders: [],
404
+ rateLimit: pruneRateLimit(obj?.rateLimit ?? null),
363
405
  };
364
406
  }
365
407
 
@@ -373,6 +415,7 @@ let GatewayAuthState = class GatewayAuthState {
373
415
  http = inject(HttpClient);
374
416
  options = inject(GATEWAY_AUTH_OPTIONS);
375
417
  router = inject(Router);
418
+ modalService = inject(ModalService);
376
419
  ssoSession = new GatewaySsoSession();
377
420
  static user(state) {
378
421
  return state.user;
@@ -413,6 +456,9 @@ let GatewayAuthState = class GatewayAuthState {
413
456
  static ssoProviders(state) {
414
457
  return state.ssoProviders;
415
458
  }
459
+ static rateLimit(state) {
460
+ return state.rateLimit;
461
+ }
416
462
  static isAdmin(state) {
417
463
  return state.user?.isAdmin || false;
418
464
  }
@@ -420,6 +466,9 @@ let GatewayAuthState = class GatewayAuthState {
420
466
  return state.user?.user || null;
421
467
  }
422
468
  login(ctx, action) {
469
+ if (this.isRateLimitActive(ctx, 'login')) {
470
+ return EMPTY;
471
+ }
423
472
  ctx.patchState({
424
473
  loading: true,
425
474
  error: null,
@@ -436,6 +485,9 @@ let GatewayAuthState = class GatewayAuthState {
436
485
  recaptchaAction: action.payload.recaptchaAction,
437
486
  })
438
487
  .pipe(mergeMap((response) => this.handleLoginResponse(ctx, response.data)), catchError((error) => {
488
+ if (this.handleRateLimit(ctx, error, 'login')) {
489
+ return of(null);
490
+ }
439
491
  ctx.dispatch(new LoginFailure(getGatewayErrorMessage(error, 'Login failed')));
440
492
  return of(null);
441
493
  }));
@@ -456,6 +508,9 @@ let GatewayAuthState = class GatewayAuthState {
456
508
  deviceToken: this.deviceToken,
457
509
  })
458
510
  .pipe(mergeMap((response) => this.handleLoginResponse(ctx, response.data)), catchError((error) => {
511
+ if (this.handleRateLimit(ctx, error, 'verifyMfa')) {
512
+ return of(null);
513
+ }
459
514
  const message = getGatewayErrorMessage(error, 'Invalid verification code');
460
515
  const shouldClearMfa = error?.status === 403;
461
516
  ctx.patchState({
@@ -477,6 +532,9 @@ let GatewayAuthState = class GatewayAuthState {
477
532
  this.navigateToLogin();
478
533
  return EMPTY;
479
534
  }
535
+ if (this.isRateLimitActive(ctx, 'resendMfa')) {
536
+ return EMPTY;
537
+ }
480
538
  ctx.patchState({ mfaResendLoading: true, error: null });
481
539
  return this.http
482
540
  .post(this.gatewayAuthMutationUrl(GATEWAY_AUTH_ENDPOINTS.resendMfa), {
@@ -491,6 +549,10 @@ let GatewayAuthState = class GatewayAuthState {
491
549
  error: null,
492
550
  });
493
551
  }), catchError((error) => {
552
+ if (this.handleRateLimit(ctx, error, 'resendMfa')) {
553
+ ctx.patchState({ mfaResendLoading: false });
554
+ return of(null);
555
+ }
494
556
  ctx.patchState({
495
557
  mfaResendLoading: false,
496
558
  error: getGatewayErrorMessage(error, 'Failed to resend code'),
@@ -549,6 +611,10 @@ let GatewayAuthState = class GatewayAuthState {
549
611
  deviceToken: this.deviceToken,
550
612
  })
551
613
  .pipe(mergeMap((response) => this.handleLoginResponse(ctx, response.data)), catchError((error) => {
614
+ if (this.handleRateLimit(ctx, error, 'ssoExchange')) {
615
+ ctx.patchState({ ssoCallbackLoading: false });
616
+ return of(null);
617
+ }
552
618
  ctx.patchState({
553
619
  ssoCallbackLoading: false,
554
620
  error: getGatewayErrorMessage(error, 'SSO sign-in failed. Please try again.'),
@@ -575,6 +641,7 @@ let GatewayAuthState = class GatewayAuthState {
575
641
  }
576
642
  logout(ctx, action) {
577
643
  const { token, refreshToken, ssoProviders } = ctx.getState();
644
+ this.modalService.closeAll();
578
645
  this.ssoSession.clearAll();
579
646
  this.toObservable(this.options.beforeLocalLogout?.(ctx)).subscribe();
580
647
  ctx.patchState({
@@ -631,6 +698,45 @@ let GatewayAuthState = class GatewayAuthState {
631
698
  clearPendingMfa(ctx) {
632
699
  ctx.patchState({ pendingMfa: null, twoFactorRequired: false });
633
700
  }
701
+ setRateLimit(ctx, action) {
702
+ ctx.patchState({
703
+ loading: false,
704
+ mfaResendLoading: false,
705
+ ssoCallbackLoading: false,
706
+ error: null,
707
+ rateLimit: action.lock,
708
+ });
709
+ }
710
+ clearRateLimit(ctx) {
711
+ ctx.patchState({ rateLimit: null });
712
+ }
713
+ isRateLimitActive(ctx, scope) {
714
+ const lock = ctx.getState().rateLimit;
715
+ if (!lock || lock.scope !== scope) {
716
+ return false;
717
+ }
718
+ if (lock.retryUntilMs && lock.retryUntilMs <= Date.now()) {
719
+ ctx.patchState({ rateLimit: null });
720
+ return false;
721
+ }
722
+ return lock.retryUntilMs === null || lock.retryUntilMs > Date.now();
723
+ }
724
+ handleRateLimit(ctx, error, scope) {
725
+ const info = extractGatewayRateLimitInfo(error);
726
+ if (!info) {
727
+ return false;
728
+ }
729
+ const retryUntilMs = info.retryAfterSeconds
730
+ ? Date.now() + info.retryAfterSeconds * 1000
731
+ : null;
732
+ ctx.dispatch(new SetRateLimit({
733
+ retryUntilMs,
734
+ totalSeconds: info.retryAfterSeconds,
735
+ message: info.message,
736
+ scope,
737
+ }));
738
+ return true;
739
+ }
634
740
  handleLoginResponse(ctx, session) {
635
741
  if (session.requiresTwoFactor) {
636
742
  ctx.patchState({
@@ -762,6 +868,12 @@ __decorate([
762
868
  __decorate([
763
869
  Action(ClearPendingMfa)
764
870
  ], GatewayAuthState.prototype, "clearPendingMfa", null);
871
+ __decorate([
872
+ Action(SetRateLimit)
873
+ ], GatewayAuthState.prototype, "setRateLimit", null);
874
+ __decorate([
875
+ Action(ClearRateLimit)
876
+ ], GatewayAuthState.prototype, "clearRateLimit", null);
765
877
  __decorate([
766
878
  Selector()
767
879
  ], GatewayAuthState, "user", null);
@@ -801,6 +913,9 @@ __decorate([
801
913
  __decorate([
802
914
  Selector()
803
915
  ], GatewayAuthState, "ssoProviders", null);
916
+ __decorate([
917
+ Selector()
918
+ ], GatewayAuthState, "rateLimit", null);
804
919
  __decorate([
805
920
  Selector()
806
921
  ], GatewayAuthState, "isAdmin", null);
@@ -815,7 +930,7 @@ GatewayAuthState = __decorate([
815
930
  ], GatewayAuthState);
816
931
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GatewayAuthState, decorators: [{
817
932
  type: Injectable
818
- }], propDecorators: { login: [], verifyMfa: [], resendMfa: [], loadSsoProviders: [], startSso: [], exchangeSsoCode: [], loginSuccess: [], loginFailure: [], logout: [], updateUserData: [], updateTokens: [], clearError: [], clearPendingMfa: [] } });
933
+ }], propDecorators: { login: [], verifyMfa: [], resendMfa: [], loadSsoProviders: [], startSso: [], exchangeSsoCode: [], loginSuccess: [], loginFailure: [], logout: [], updateUserData: [], updateTokens: [], clearError: [], clearPendingMfa: [], setRateLimit: [], clearRateLimit: [] } });
819
934
 
820
935
  class GatewayAuthFacade {
821
936
  store = inject(Store);
@@ -832,6 +947,7 @@ class GatewayAuthFacade {
832
947
  twoFactorRequired = select(GatewayAuthState.twoFactorRequired);
833
948
  pendingMfa = select(GatewayAuthState.pendingMfa);
834
949
  ssoProviders = select(GatewayAuthState.ssoProviders);
950
+ rateLimit = select(GatewayAuthState.rateLimit);
835
951
  isAdmin = select(GatewayAuthState.isAdmin);
836
952
  userDetails = select(GatewayAuthState.userDetails);
837
953
  hasError = computed(() => this.error() !== null, ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
@@ -883,6 +999,9 @@ class GatewayAuthFacade {
883
999
  clearPendingMfa() {
884
1000
  this.store.dispatch(new ClearPendingMfa());
885
1001
  }
1002
+ clearRateLimit() {
1003
+ this.store.dispatch(new ClearRateLimit());
1004
+ }
886
1005
  hasRole(role) {
887
1006
  const userRoles = this.user()?.user?.roles;
888
1007
  return Array.isArray(userRoles) ? userRoles.includes(role) : false;
@@ -1145,6 +1264,43 @@ class GatewayLoginPage {
1145
1264
  error = this.authFacade.error;
1146
1265
  passwordVisible = signal(false, ...(ngDevMode ? [{ debugName: "passwordVisible" }] : /* istanbul ignore next */ []));
1147
1266
  selectedLanguage = signal('', ...(ngDevMode ? [{ debugName: "selectedLanguage" }] : /* istanbul ignore next */ []));
1267
+ nowMs = signal(Date.now(), ...(ngDevMode ? [{ debugName: "nowMs" }] : /* istanbul ignore next */ []));
1268
+ rateLimit = computed(() => {
1269
+ const lock = this.authFacade.rateLimit();
1270
+ return lock && lock.scope === 'login' ? lock : null;
1271
+ }, ...(ngDevMode ? [{ debugName: "rateLimit" }] : /* istanbul ignore next */ []));
1272
+ rateLimitSecondsLeft = computed(() => {
1273
+ const lock = this.rateLimit();
1274
+ if (!lock?.retryUntilMs)
1275
+ return null;
1276
+ return Math.max(0, Math.ceil((lock.retryUntilMs - this.nowMs()) / 1000));
1277
+ }, ...(ngDevMode ? [{ debugName: "rateLimitSecondsLeft" }] : /* istanbul ignore next */ []));
1278
+ rateLimitProgress = computed(() => {
1279
+ const lock = this.rateLimit();
1280
+ const left = this.rateLimitSecondsLeft();
1281
+ if (!lock?.totalSeconds || left === null)
1282
+ return 0;
1283
+ return Math.max(0, Math.min(100, (left / lock.totalSeconds) * 100));
1284
+ }, ...(ngDevMode ? [{ debugName: "rateLimitProgress" }] : /* istanbul ignore next */ []));
1285
+ rateLimitCountdownLabel = computed(() => {
1286
+ const left = this.rateLimitSecondsLeft();
1287
+ if (left === null)
1288
+ return '';
1289
+ const minutes = Math.floor(left / 60);
1290
+ const seconds = left % 60;
1291
+ return minutes > 0
1292
+ ? `${minutes}:${String(seconds).padStart(2, '0')}`
1293
+ : `${seconds}s`;
1294
+ }, ...(ngDevMode ? [{ debugName: "rateLimitCountdownLabel" }] : /* istanbul ignore next */ []));
1295
+ isRateLimited = computed(() => {
1296
+ const lock = this.rateLimit();
1297
+ if (!lock)
1298
+ return false;
1299
+ if (lock.retryUntilMs === null)
1300
+ return true;
1301
+ return (this.rateLimitSecondsLeft() ?? 0) > 0;
1302
+ }, ...(ngDevMode ? [{ debugName: "isRateLimited" }] : /* istanbul ignore next */ []));
1303
+ submitDisabled = computed(() => this.loginForm.invalid || this.loading() || this.isRateLimited(), ...(ngDevMode ? [{ debugName: "submitDisabled" }] : /* istanbul ignore next */ []));
1148
1304
  loginOptions = computed(() => this.options.loginPage, ...(ngDevMode ? [{ debugName: "loginOptions" }] : /* istanbul ignore next */ []));
1149
1305
  defaultDisplayData = computed(() => this.brandDisplayFacade.defaultBrandDisplayData(), ...(ngDevMode ? [{ debugName: "defaultDisplayData" }] : /* istanbul ignore next */ []));
1150
1306
  loginData = computed(() => this.branding.loginData(), ...(ngDevMode ? [{ debugName: "loginData" }] : /* istanbul ignore next */ []));
@@ -1207,6 +1363,24 @@ class GatewayLoginPage {
1207
1363
  effect(() => {
1208
1364
  this.selectedLanguage.set(this.activeLanguage());
1209
1365
  });
1366
+ effect((onCleanup) => {
1367
+ const lock = this.rateLimit();
1368
+ if (!lock?.retryUntilMs) {
1369
+ return;
1370
+ }
1371
+ if (lock.retryUntilMs <= Date.now()) {
1372
+ this.authFacade.clearRateLimit();
1373
+ return;
1374
+ }
1375
+ this.nowMs.set(Date.now());
1376
+ const id = window.setInterval(() => {
1377
+ this.nowMs.set(Date.now());
1378
+ if (lock.retryUntilMs <= Date.now()) {
1379
+ this.authFacade.clearRateLimit();
1380
+ }
1381
+ }, 1000);
1382
+ onCleanup(() => window.clearInterval(id));
1383
+ });
1210
1384
  }
1211
1385
  ngOnInit() {
1212
1386
  this.brandDisplayFacade.getBrandDisplay();
@@ -1235,6 +1409,9 @@ class GatewayLoginPage {
1235
1409
  this.loginForm.markAllAsTouched();
1236
1410
  return;
1237
1411
  }
1412
+ if (this.isRateLimited()) {
1413
+ return;
1414
+ }
1238
1415
  const { username, password } = this.loginForm.getRawValue();
1239
1416
  this.authFacade.login({
1240
1417
  userName: username,
@@ -1244,7 +1421,7 @@ class GatewayLoginPage {
1244
1421
  });
1245
1422
  }
1246
1423
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GatewayLoginPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
1247
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: GatewayLoginPage, isStandalone: true, selector: "mt-gateway-login-page", ngImport: i0, template: "@if (formPosition()) {\n <ng-container *transloco=\"let t; prefix: translationPrefix()\">\n <div\n class=\"w-screen h-screen flex overflow-hidden\"\n [style]=\"pageStyles()\"\n [style.backgroundImage]=\"\n backgroundImage()\n ? (backgroundImage()\n | secureImage: false : undefined : imageBasePath())\n : null\n \"\n >\n @if (formPosition() === \"end\") {\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\n }\n\n <div [class]=\"formContainerClasses()\">\n @if (formPosition() === \"center\") {\n <div\n class=\"flex flex-col items-center text-center mb-8 space-y-4\"\n [style.color]=\"textColor()\"\n >\n <img\n [src]=\"\n logoUrl()\n ? (logoUrl()\n | secureImage: true : undefined : imageBasePath())\n : defaultLogoUrl()\n \"\n alt=\"BrandLogo\"\n class=\"h-20 mx-auto\"\n />\n @if (productTagline(); as tagline) {\n <h3 class=\"text-2xl font-medium\">{{ tagline }}</h3>\n }\n </div>\n }\n\n <div\n class=\"glass-card relative w-full max-w-md rounded-xl pb-8 px-10 pt-12 space-y-[4vh]\"\n [style.color]=\"textColor()\"\n >\n <div class=\"text-center space-y-[4vh]\">\n <h2 class=\"text-3xl font-bold\">{{ t(\"signin\") }}</h2>\n <p>{{ t(\"welcome-subtitle\") }}</p>\n </div>\n\n <form\n [formGroup]=\"loginForm\"\n (ngSubmit)=\"onSubmit()\"\n class=\"space-y-[1rem]\"\n >\n <mt-text-field [label]=\"t('username')\" formControlName=\"username\" />\n\n <div class=\"relative\">\n <mt-text-field\n [label]=\"t('password')\"\n [type]=\"passwordVisible() ? 'text' : 'password'\"\n formControlName=\"password\"\n />\n <button\n type=\"button\"\n class=\"password-toggle-btn cursor-pointer absolute end-3 top-7 h-10 flex items-center justify-center text-lg leading-none z-10\"\n [attr.aria-label]=\"\n passwordVisible() ? t('hide-password') : t('show-password')\n \"\n (click)=\"togglePasswordVisibility()\"\n ></button>\n </div>\n\n <mt-button\n [label]=\"t('signin')\"\n size=\"large\"\n severity=\"primary\"\n fluid\n type=\"submit\"\n [loading]=\"loading()\"\n [disabled]=\"loginForm.invalid || loading()\"\n styleClass=\"mt-2\"\n />\n\n @if (error(); as errorMessage) {\n <p class=\"text-sm text-red-500 text-center\">\n {{ errorMessage }}\n </p>\n }\n\n <mt-gateway-sso-buttons [dividerLabel]=\"t('or')\" />\n\n @if (showLanguageToggle()) {\n <div class=\"pt-1 w-35 mx-auto\">\n <mt-select-field\n [(ngModel)]=\"selectedLanguage\"\n [ngModelOptions]=\"{ standalone: true }\"\n [options]=\"languageOptions()\"\n optionLabel=\"label\"\n optionValue=\"id\"\n (onChange)=\"onLanguageChange()\"\n size=\"small\"\n />\n </div>\n }\n </form>\n\n <p\n class=\"absolute text-center text-xs text-gray-400 pt-6 bottom-4 left-1/2 -translate-x-1/2 w-full\"\n >\n {{ t(\"copyright\") }}\n </p>\n </div>\n </div>\n\n @if (formPosition() === \"start\") {\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\n }\n </div>\n\n <ng-template #brandPanel>\n <div\n class=\"hidden md:flex flex-col justify-center items-start w-1/2 p-12\"\n [style.color]=\"textColor()\"\n >\n <div class=\"w-full max-w-full rounded-xl p-1 space-y-2 h-1/2 ps-24\">\n <div [class]=\"logoAlignment()\">\n <img\n [src]=\"\n logoUrl()\n ? (logoUrl()\n | secureImage: true : undefined : imageBasePath())\n : defaultLogoUrl()\n \"\n alt=\"Logo\"\n class=\"h-20\"\n />\n </div>\n @if (productTagline(); as tagline) {\n <h3 class=\"text-2xl\">{{ tagline }}</h3>\n }\n </div>\n </div>\n </ng-template>\n </ng-container>\n} @else {\n <div class=\"w-screen h-screen flex items-center justify-center\">\n <mt-icon\n class=\"text-4xl text-primary animate-spin animate-duration-2000\"\n icon=\"general.loading-02\"\n />\n </div>\n}\n", styles: [":host{display:block}.glass-card{background:#ffffff26;backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);box-shadow:0 8px 32px #0003;color:#fff}.glass-card *:not(input):not(button):not([class*=p-]){color:inherit!important}.password-toggle-btn{color:#374151d9;transition:color .15s ease}.password-toggle-btn:hover{color:#111827}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: GatewaySsoButtons, selector: "mt-gateway-sso-buttons", inputs: ["dividerLabel"] }, { kind: "pipe", type: SecureImagePipe, name: "secureImage" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1424
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: GatewayLoginPage, isStandalone: true, selector: "mt-gateway-login-page", ngImport: i0, template: "@if (formPosition()) {\r\n <ng-container *transloco=\"let t; prefix: translationPrefix()\">\r\n <div\r\n class=\"w-screen h-screen flex overflow-hidden\"\r\n [style]=\"pageStyles()\"\r\n [style.backgroundImage]=\"\r\n backgroundImage()\r\n ? (backgroundImage()\r\n | secureImage: false : undefined : imageBasePath())\r\n : null\r\n \"\r\n >\r\n @if (formPosition() === \"end\") {\r\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\r\n }\r\n\r\n <div [class]=\"formContainerClasses()\">\r\n @if (formPosition() === \"center\") {\r\n <div\r\n class=\"flex flex-col items-center text-center mb-8 space-y-4\"\r\n [style.color]=\"textColor()\"\r\n >\r\n <img\r\n [src]=\"\r\n logoUrl()\r\n ? (logoUrl()\r\n | secureImage: true : undefined : imageBasePath())\r\n : defaultLogoUrl()\r\n \"\r\n alt=\"BrandLogo\"\r\n class=\"h-20 mx-auto\"\r\n />\r\n @if (productTagline(); as tagline) {\r\n <h3 class=\"text-2xl font-medium\">{{ tagline }}</h3>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"glass-card relative w-full max-w-md rounded-xl pb-8 px-10 pt-12 space-y-[4vh]\"\r\n [style.color]=\"textColor()\"\r\n >\r\n <div class=\"text-center space-y-[4vh]\">\r\n <h2 class=\"text-3xl font-bold\">{{ t(\"signin\") }}</h2>\r\n <p>{{ t(\"welcome-subtitle\") }}</p>\r\n </div>\r\n\r\n <form\r\n [formGroup]=\"loginForm\"\r\n (ngSubmit)=\"onSubmit()\"\r\n class=\"space-y-[1rem]\"\r\n >\r\n <mt-text-field [label]=\"t('username')\" formControlName=\"username\" />\r\n\r\n <div class=\"relative\">\r\n <mt-text-field\r\n [label]=\"t('password')\"\r\n [type]=\"passwordVisible() ? 'text' : 'password'\"\r\n formControlName=\"password\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"password-toggle-btn cursor-pointer absolute end-3 top-7 h-10 flex items-center justify-center text-lg leading-none z-10\"\r\n [attr.aria-label]=\"\r\n passwordVisible() ? t('hide-password') : t('show-password')\r\n \"\r\n (click)=\"togglePasswordVisibility()\"\r\n ></button>\r\n </div>\r\n\r\n <mt-button\r\n [label]=\"\r\n isRateLimited() && rateLimitSecondsLeft() !== null\r\n ? t('rate-limit-retry-in', {\r\n time: rateLimitCountdownLabel(),\r\n })\r\n : t('signin')\r\n \"\r\n size=\"large\"\r\n severity=\"primary\"\r\n fluid\r\n type=\"submit\"\r\n [loading]=\"loading()\"\r\n [disabled]=\"submitDisabled()\"\r\n styleClass=\"mt-2\"\r\n />\r\n\r\n @if (rateLimit(); as lock) {\r\n <div\r\n class=\"rate-limit-banner flex items-start gap-3 rounded-lg border border-red-300/40 bg-red-500/10 p-3 text-sm\"\r\n role=\"alert\"\r\n aria-live=\"polite\"\r\n >\r\n <mt-icon\r\n class=\"text-base text-red-500 mt-0.5 shrink-0\"\r\n icon=\"alert.alert-circle\"\r\n />\r\n <div class=\"flex-1 min-w-0 space-y-1\">\r\n <p class=\"font-medium\">{{ t(\"rate-limit-title\") }}</p>\r\n <p class=\"text-xs opacity-90\">\r\n @if (rateLimitSecondsLeft() !== null) {\r\n {{\r\n t(\"rate-limit-countdown\", {\r\n time: rateLimitCountdownLabel(),\r\n })\r\n }}\r\n } @else {\r\n {{ lock.message || t(\"rate-limit-blocked\") }}\r\n }\r\n </p>\r\n @if (rateLimitSecondsLeft() !== null) {\r\n <div\r\n class=\"rate-limit-progress h-1 w-full overflow-hidden rounded-full bg-red-500/20\"\r\n >\r\n <div\r\n class=\"h-full bg-red-500 transition-[width] duration-1000 ease-linear\"\r\n [style.width.%]=\"rateLimitProgress()\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else if (error(); as errorMessage) {\r\n <p class=\"text-sm text-red-500 text-center\">\r\n {{ errorMessage }}\r\n </p>\r\n }\r\n\r\n <mt-gateway-sso-buttons [dividerLabel]=\"t('or')\" />\r\n\r\n @if (showLanguageToggle()) {\r\n <div class=\"pt-1 w-35 mx-auto\">\r\n <mt-select-field\r\n [(ngModel)]=\"selectedLanguage\"\r\n [ngModelOptions]=\"{ standalone: true }\"\r\n [options]=\"languageOptions()\"\r\n optionLabel=\"label\"\r\n optionValue=\"id\"\r\n (onChange)=\"onLanguageChange()\"\r\n size=\"small\"\r\n />\r\n </div>\r\n }\r\n </form>\r\n\r\n <p\r\n class=\"absolute text-center text-xs text-gray-400 pt-6 bottom-4 left-1/2 -translate-x-1/2 w-full\"\r\n >\r\n {{ t(\"copyright\") }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n @if (formPosition() === \"start\") {\r\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\r\n }\r\n </div>\r\n\r\n <ng-template #brandPanel>\r\n <div\r\n class=\"hidden md:flex flex-col justify-center items-start w-1/2 p-12\"\r\n [style.color]=\"textColor()\"\r\n >\r\n <div class=\"w-full max-w-full rounded-xl p-1 space-y-2 h-1/2 ps-24\">\r\n <div [class]=\"logoAlignment()\">\r\n <img\r\n [src]=\"\r\n logoUrl()\r\n ? (logoUrl()\r\n | secureImage: true : undefined : imageBasePath())\r\n : defaultLogoUrl()\r\n \"\r\n alt=\"Logo\"\r\n class=\"h-20\"\r\n />\r\n </div>\r\n @if (productTagline(); as tagline) {\r\n <h3 class=\"text-2xl\">{{ tagline }}</h3>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n </ng-container>\r\n} @else {\r\n <div class=\"w-screen h-screen flex items-center justify-center\">\r\n <mt-icon\r\n class=\"text-4xl text-primary animate-spin animate-duration-2000\"\r\n icon=\"general.loading-02\"\r\n />\r\n </div>\r\n}\r\n", styles: [":host{display:block}.glass-card{background:#ffffff26;backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);box-shadow:0 8px 32px #0003;color:#fff}.glass-card *:not(input):not(button):not([class*=p-]){color:inherit!important}.password-toggle-btn{color:#374151d9;transition:color .15s ease}.password-toggle-btn:hover{color:#111827}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: SelectField, selector: "mt-select-field", inputs: ["field", "hint", "label", "placeholder", "hasPlaceholderPrefix", "class", "readonly", "pInputs", "options", "optionValue", "optionLabel", "filter", "filterBy", "dataKey", "showClear", "clearAfterSelect", "required", "group", "size", "optionGroupLabel", "optionGroupChildren", "loading", "optionIcon", "optionIconColor", "optionIconShape", "optionAvatarShape", "optionGroupIcon", "optionGroupIconColor", "optionGroupIconShape", "optionGroupAvatarShape", "markCurrentUser"], outputs: ["onChange"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: GatewaySsoButtons, selector: "mt-gateway-sso-buttons", inputs: ["dividerLabel"] }, { kind: "pipe", type: SecureImagePipe, name: "secureImage" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1248
1425
  }
1249
1426
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GatewayLoginPage, decorators: [{
1250
1427
  type: Component,
@@ -1259,7 +1436,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1259
1436
  SecureImagePipe,
1260
1437
  Icon,
1261
1438
  GatewaySsoButtons,
1262
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (formPosition()) {\n <ng-container *transloco=\"let t; prefix: translationPrefix()\">\n <div\n class=\"w-screen h-screen flex overflow-hidden\"\n [style]=\"pageStyles()\"\n [style.backgroundImage]=\"\n backgroundImage()\n ? (backgroundImage()\n | secureImage: false : undefined : imageBasePath())\n : null\n \"\n >\n @if (formPosition() === \"end\") {\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\n }\n\n <div [class]=\"formContainerClasses()\">\n @if (formPosition() === \"center\") {\n <div\n class=\"flex flex-col items-center text-center mb-8 space-y-4\"\n [style.color]=\"textColor()\"\n >\n <img\n [src]=\"\n logoUrl()\n ? (logoUrl()\n | secureImage: true : undefined : imageBasePath())\n : defaultLogoUrl()\n \"\n alt=\"BrandLogo\"\n class=\"h-20 mx-auto\"\n />\n @if (productTagline(); as tagline) {\n <h3 class=\"text-2xl font-medium\">{{ tagline }}</h3>\n }\n </div>\n }\n\n <div\n class=\"glass-card relative w-full max-w-md rounded-xl pb-8 px-10 pt-12 space-y-[4vh]\"\n [style.color]=\"textColor()\"\n >\n <div class=\"text-center space-y-[4vh]\">\n <h2 class=\"text-3xl font-bold\">{{ t(\"signin\") }}</h2>\n <p>{{ t(\"welcome-subtitle\") }}</p>\n </div>\n\n <form\n [formGroup]=\"loginForm\"\n (ngSubmit)=\"onSubmit()\"\n class=\"space-y-[1rem]\"\n >\n <mt-text-field [label]=\"t('username')\" formControlName=\"username\" />\n\n <div class=\"relative\">\n <mt-text-field\n [label]=\"t('password')\"\n [type]=\"passwordVisible() ? 'text' : 'password'\"\n formControlName=\"password\"\n />\n <button\n type=\"button\"\n class=\"password-toggle-btn cursor-pointer absolute end-3 top-7 h-10 flex items-center justify-center text-lg leading-none z-10\"\n [attr.aria-label]=\"\n passwordVisible() ? t('hide-password') : t('show-password')\n \"\n (click)=\"togglePasswordVisibility()\"\n ></button>\n </div>\n\n <mt-button\n [label]=\"t('signin')\"\n size=\"large\"\n severity=\"primary\"\n fluid\n type=\"submit\"\n [loading]=\"loading()\"\n [disabled]=\"loginForm.invalid || loading()\"\n styleClass=\"mt-2\"\n />\n\n @if (error(); as errorMessage) {\n <p class=\"text-sm text-red-500 text-center\">\n {{ errorMessage }}\n </p>\n }\n\n <mt-gateway-sso-buttons [dividerLabel]=\"t('or')\" />\n\n @if (showLanguageToggle()) {\n <div class=\"pt-1 w-35 mx-auto\">\n <mt-select-field\n [(ngModel)]=\"selectedLanguage\"\n [ngModelOptions]=\"{ standalone: true }\"\n [options]=\"languageOptions()\"\n optionLabel=\"label\"\n optionValue=\"id\"\n (onChange)=\"onLanguageChange()\"\n size=\"small\"\n />\n </div>\n }\n </form>\n\n <p\n class=\"absolute text-center text-xs text-gray-400 pt-6 bottom-4 left-1/2 -translate-x-1/2 w-full\"\n >\n {{ t(\"copyright\") }}\n </p>\n </div>\n </div>\n\n @if (formPosition() === \"start\") {\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\n }\n </div>\n\n <ng-template #brandPanel>\n <div\n class=\"hidden md:flex flex-col justify-center items-start w-1/2 p-12\"\n [style.color]=\"textColor()\"\n >\n <div class=\"w-full max-w-full rounded-xl p-1 space-y-2 h-1/2 ps-24\">\n <div [class]=\"logoAlignment()\">\n <img\n [src]=\"\n logoUrl()\n ? (logoUrl()\n | secureImage: true : undefined : imageBasePath())\n : defaultLogoUrl()\n \"\n alt=\"Logo\"\n class=\"h-20\"\n />\n </div>\n @if (productTagline(); as tagline) {\n <h3 class=\"text-2xl\">{{ tagline }}</h3>\n }\n </div>\n </div>\n </ng-template>\n </ng-container>\n} @else {\n <div class=\"w-screen h-screen flex items-center justify-center\">\n <mt-icon\n class=\"text-4xl text-primary animate-spin animate-duration-2000\"\n icon=\"general.loading-02\"\n />\n </div>\n}\n", styles: [":host{display:block}.glass-card{background:#ffffff26;backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);box-shadow:0 8px 32px #0003;color:#fff}.glass-card *:not(input):not(button):not([class*=p-]){color:inherit!important}.password-toggle-btn{color:#374151d9;transition:color .15s ease}.password-toggle-btn:hover{color:#111827}\n"] }]
1439
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (formPosition()) {\r\n <ng-container *transloco=\"let t; prefix: translationPrefix()\">\r\n <div\r\n class=\"w-screen h-screen flex overflow-hidden\"\r\n [style]=\"pageStyles()\"\r\n [style.backgroundImage]=\"\r\n backgroundImage()\r\n ? (backgroundImage()\r\n | secureImage: false : undefined : imageBasePath())\r\n : null\r\n \"\r\n >\r\n @if (formPosition() === \"end\") {\r\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\r\n }\r\n\r\n <div [class]=\"formContainerClasses()\">\r\n @if (formPosition() === \"center\") {\r\n <div\r\n class=\"flex flex-col items-center text-center mb-8 space-y-4\"\r\n [style.color]=\"textColor()\"\r\n >\r\n <img\r\n [src]=\"\r\n logoUrl()\r\n ? (logoUrl()\r\n | secureImage: true : undefined : imageBasePath())\r\n : defaultLogoUrl()\r\n \"\r\n alt=\"BrandLogo\"\r\n class=\"h-20 mx-auto\"\r\n />\r\n @if (productTagline(); as tagline) {\r\n <h3 class=\"text-2xl font-medium\">{{ tagline }}</h3>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"glass-card relative w-full max-w-md rounded-xl pb-8 px-10 pt-12 space-y-[4vh]\"\r\n [style.color]=\"textColor()\"\r\n >\r\n <div class=\"text-center space-y-[4vh]\">\r\n <h2 class=\"text-3xl font-bold\">{{ t(\"signin\") }}</h2>\r\n <p>{{ t(\"welcome-subtitle\") }}</p>\r\n </div>\r\n\r\n <form\r\n [formGroup]=\"loginForm\"\r\n (ngSubmit)=\"onSubmit()\"\r\n class=\"space-y-[1rem]\"\r\n >\r\n <mt-text-field [label]=\"t('username')\" formControlName=\"username\" />\r\n\r\n <div class=\"relative\">\r\n <mt-text-field\r\n [label]=\"t('password')\"\r\n [type]=\"passwordVisible() ? 'text' : 'password'\"\r\n formControlName=\"password\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"password-toggle-btn cursor-pointer absolute end-3 top-7 h-10 flex items-center justify-center text-lg leading-none z-10\"\r\n [attr.aria-label]=\"\r\n passwordVisible() ? t('hide-password') : t('show-password')\r\n \"\r\n (click)=\"togglePasswordVisibility()\"\r\n ></button>\r\n </div>\r\n\r\n <mt-button\r\n [label]=\"\r\n isRateLimited() && rateLimitSecondsLeft() !== null\r\n ? t('rate-limit-retry-in', {\r\n time: rateLimitCountdownLabel(),\r\n })\r\n : t('signin')\r\n \"\r\n size=\"large\"\r\n severity=\"primary\"\r\n fluid\r\n type=\"submit\"\r\n [loading]=\"loading()\"\r\n [disabled]=\"submitDisabled()\"\r\n styleClass=\"mt-2\"\r\n />\r\n\r\n @if (rateLimit(); as lock) {\r\n <div\r\n class=\"rate-limit-banner flex items-start gap-3 rounded-lg border border-red-300/40 bg-red-500/10 p-3 text-sm\"\r\n role=\"alert\"\r\n aria-live=\"polite\"\r\n >\r\n <mt-icon\r\n class=\"text-base text-red-500 mt-0.5 shrink-0\"\r\n icon=\"alert.alert-circle\"\r\n />\r\n <div class=\"flex-1 min-w-0 space-y-1\">\r\n <p class=\"font-medium\">{{ t(\"rate-limit-title\") }}</p>\r\n <p class=\"text-xs opacity-90\">\r\n @if (rateLimitSecondsLeft() !== null) {\r\n {{\r\n t(\"rate-limit-countdown\", {\r\n time: rateLimitCountdownLabel(),\r\n })\r\n }}\r\n } @else {\r\n {{ lock.message || t(\"rate-limit-blocked\") }}\r\n }\r\n </p>\r\n @if (rateLimitSecondsLeft() !== null) {\r\n <div\r\n class=\"rate-limit-progress h-1 w-full overflow-hidden rounded-full bg-red-500/20\"\r\n >\r\n <div\r\n class=\"h-full bg-red-500 transition-[width] duration-1000 ease-linear\"\r\n [style.width.%]=\"rateLimitProgress()\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @else if (error(); as errorMessage) {\r\n <p class=\"text-sm text-red-500 text-center\">\r\n {{ errorMessage }}\r\n </p>\r\n }\r\n\r\n <mt-gateway-sso-buttons [dividerLabel]=\"t('or')\" />\r\n\r\n @if (showLanguageToggle()) {\r\n <div class=\"pt-1 w-35 mx-auto\">\r\n <mt-select-field\r\n [(ngModel)]=\"selectedLanguage\"\r\n [ngModelOptions]=\"{ standalone: true }\"\r\n [options]=\"languageOptions()\"\r\n optionLabel=\"label\"\r\n optionValue=\"id\"\r\n (onChange)=\"onLanguageChange()\"\r\n size=\"small\"\r\n />\r\n </div>\r\n }\r\n </form>\r\n\r\n <p\r\n class=\"absolute text-center text-xs text-gray-400 pt-6 bottom-4 left-1/2 -translate-x-1/2 w-full\"\r\n >\r\n {{ t(\"copyright\") }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n @if (formPosition() === \"start\") {\r\n <ng-container [ngTemplateOutlet]=\"brandPanel\" />\r\n }\r\n </div>\r\n\r\n <ng-template #brandPanel>\r\n <div\r\n class=\"hidden md:flex flex-col justify-center items-start w-1/2 p-12\"\r\n [style.color]=\"textColor()\"\r\n >\r\n <div class=\"w-full max-w-full rounded-xl p-1 space-y-2 h-1/2 ps-24\">\r\n <div [class]=\"logoAlignment()\">\r\n <img\r\n [src]=\"\r\n logoUrl()\r\n ? (logoUrl()\r\n | secureImage: true : undefined : imageBasePath())\r\n : defaultLogoUrl()\r\n \"\r\n alt=\"Logo\"\r\n class=\"h-20\"\r\n />\r\n </div>\r\n @if (productTagline(); as tagline) {\r\n <h3 class=\"text-2xl\">{{ tagline }}</h3>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n </ng-container>\r\n} @else {\r\n <div class=\"w-screen h-screen flex items-center justify-center\">\r\n <mt-icon\r\n class=\"text-4xl text-primary animate-spin animate-duration-2000\"\r\n icon=\"general.loading-02\"\r\n />\r\n </div>\r\n}\r\n", styles: [":host{display:block}.glass-card{background:#ffffff26;backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);box-shadow:0 8px 32px #0003;color:#fff}.glass-card *:not(input):not(button):not([class*=p-]){color:inherit!important}.password-toggle-btn{color:#374151d9;transition:color .15s ease}.password-toggle-btn:hover{color:#111827}\n"] }]
1263
1440
  }], ctorParameters: () => [] });
1264
1441
 
1265
1442
  class GatewayMfa {
@@ -1378,5 +1555,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1378
1555
  * Generated bundle index. Do not edit.
1379
1556
  */
1380
1557
 
1381
- export { AUTH_STATE_DEFAULTS, ClearError, ClearPendingMfa, 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, GatewayAuthFacade, GatewayAuthState, GatewayLoginPage, GatewayMfa, GatewaySsoButtons, GatewaySsoCallback, GatewaySsoSession, LoadSsoProviders, Login, LoginFailure, LoginSuccess, Logout, ResendMfa, StartSso, UpdateTokens, UpdateUserData, VerifyMfa, buildGatewayUrl, buildSsoStartUrl, createSecureClientState, gatewayAuthInterceptor, getGatewayErrorMessage, hasGatewayTokens, isExpired, isGatewayAuthRequestUrl, mapGatewayTokens, mapGatewayUser, normalizeGatewayBase, readPersistedGatewayAuthTokens, resolveAccessTokenRefreshSkewMs, resolveApiDateValue, resolveGatewayAuthPath, resolveGatewayDeviceToken, sanitizePersistedAuthState, withGatewayAuthNgswBypass };
1558
+ 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 };
1382
1559
  //# sourceMappingURL=masterteam-gateway-auth.mjs.map