@masterteam/gateway-auth 0.0.12 → 0.0.14
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 {
|
|
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,44 @@ 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() || true, ...(ngDevMode ? [{ debugName: "submitDisabled" }] : /* istanbul ignore next */ []));
|
|
1304
|
+
// this.isRateLimited()
|
|
1148
1305
|
loginOptions = computed(() => this.options.loginPage, ...(ngDevMode ? [{ debugName: "loginOptions" }] : /* istanbul ignore next */ []));
|
|
1149
1306
|
defaultDisplayData = computed(() => this.brandDisplayFacade.defaultBrandDisplayData(), ...(ngDevMode ? [{ debugName: "defaultDisplayData" }] : /* istanbul ignore next */ []));
|
|
1150
1307
|
loginData = computed(() => this.branding.loginData(), ...(ngDevMode ? [{ debugName: "loginData" }] : /* istanbul ignore next */ []));
|
|
@@ -1207,6 +1364,24 @@ class GatewayLoginPage {
|
|
|
1207
1364
|
effect(() => {
|
|
1208
1365
|
this.selectedLanguage.set(this.activeLanguage());
|
|
1209
1366
|
});
|
|
1367
|
+
effect((onCleanup) => {
|
|
1368
|
+
const lock = this.rateLimit();
|
|
1369
|
+
if (!lock?.retryUntilMs) {
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (lock.retryUntilMs <= Date.now()) {
|
|
1373
|
+
this.authFacade.clearRateLimit();
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
this.nowMs.set(Date.now());
|
|
1377
|
+
const id = window.setInterval(() => {
|
|
1378
|
+
this.nowMs.set(Date.now());
|
|
1379
|
+
if (lock.retryUntilMs <= Date.now()) {
|
|
1380
|
+
this.authFacade.clearRateLimit();
|
|
1381
|
+
}
|
|
1382
|
+
}, 1000);
|
|
1383
|
+
onCleanup(() => window.clearInterval(id));
|
|
1384
|
+
});
|
|
1210
1385
|
}
|
|
1211
1386
|
ngOnInit() {
|
|
1212
1387
|
this.brandDisplayFacade.getBrandDisplay();
|
|
@@ -1235,6 +1410,9 @@ class GatewayLoginPage {
|
|
|
1235
1410
|
this.loginForm.markAllAsTouched();
|
|
1236
1411
|
return;
|
|
1237
1412
|
}
|
|
1413
|
+
if (this.isRateLimited()) {
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1238
1416
|
const { username, password } = this.loginForm.getRawValue();
|
|
1239
1417
|
this.authFacade.login({
|
|
1240
1418
|
userName: username,
|
|
@@ -1244,7 +1422,7 @@ class GatewayLoginPage {
|
|
|
1244
1422
|
});
|
|
1245
1423
|
}
|
|
1246
1424
|
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 });
|
|
1425
|
+
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
1426
|
}
|
|
1249
1427
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: GatewayLoginPage, decorators: [{
|
|
1250
1428
|
type: Component,
|
|
@@ -1259,7 +1437,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1259
1437
|
SecureImagePipe,
|
|
1260
1438
|
Icon,
|
|
1261
1439
|
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]=\"
|
|
1440
|
+
], 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
1441
|
}], ctorParameters: () => [] });
|
|
1264
1442
|
|
|
1265
1443
|
class GatewayMfa {
|
|
@@ -1378,5 +1556,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1378
1556
|
* Generated bundle index. Do not edit.
|
|
1379
1557
|
*/
|
|
1380
1558
|
|
|
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 };
|
|
1559
|
+
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
1560
|
//# sourceMappingURL=masterteam-gateway-auth.mjs.map
|