@c8y/login 1022.44.7 → 1022.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/app/login/login.component.html +124 -125
- package/src/app/login/login.component.ts +30 -16
- package/src/app/login/login.model.ts +5 -0
- package/src/app/login/login.service.ts +23 -1
- package/src/app/login/recover-password/recover-password.component.html +34 -24
- package/src/app/login/recover-password/recover-password.component.ts +23 -7
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/login",
|
|
3
|
-
"version": "1022.
|
|
3
|
+
"version": "1022.46.1",
|
|
4
4
|
"description": "This package is used to scaffold a login application for Cumulocity IoT.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@c8y/style": "1022.
|
|
7
|
-
"@c8y/ngx-components": "1022.
|
|
8
|
-
"@c8y/client": "1022.
|
|
9
|
-
"@c8y/bootstrap": "1022.
|
|
6
|
+
"@c8y/style": "1022.46.1",
|
|
7
|
+
"@c8y/ngx-components": "1022.46.1",
|
|
8
|
+
"@c8y/client": "1022.46.1",
|
|
9
|
+
"@c8y/bootstrap": "1022.46.1",
|
|
10
10
|
"@angular/cdk": "^19.2.19",
|
|
11
11
|
"monaco-editor": "~0.53.0",
|
|
12
12
|
"ngx-bootstrap": "19.0.2",
|
|
13
13
|
"rxjs": "7.8.1"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@c8y/options": "1022.
|
|
17
|
-
"@c8y/devkit": "1022.
|
|
16
|
+
"@c8y/options": "1022.46.1",
|
|
17
|
+
"@c8y/devkit": "1022.46.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@angular/common": ">=19 <20"
|
|
@@ -1,128 +1,127 @@
|
|
|
1
1
|
<div class="login-panel {{ platformAnimationSrc ? 'isc8y' : '' }}">
|
|
2
|
-
|
|
3
|
-
class="square animated fadeIn"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
</div>
|
|
2
|
+
@if (platformAnimationSrc) {
|
|
3
|
+
<div class="square animated fadeIn">
|
|
4
|
+
<img [src]="platformAnimationSrc" />
|
|
5
|
+
</div>
|
|
6
|
+
}
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
class="login-form animated fadeIn"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class="
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
</
|
|
124
|
-
|
|
125
|
-
</
|
|
126
|
-
</
|
|
127
|
-
|
|
8
|
+
@if (currentView !== LOGIN_VIEWS.None) {
|
|
9
|
+
<div class="login-form animated fadeIn">
|
|
10
|
+
<main class="card-block p-b-0 form-group-lg">
|
|
11
|
+
<span class="mainlogo {{ !isBrandLogoSet ? 'c8y-logo' : '' }}"></span>
|
|
12
|
+
<c8y-alert-outlet
|
|
13
|
+
class="m-b-24 d-block"
|
|
14
|
+
position="static"
|
|
15
|
+
></c8y-alert-outlet>
|
|
16
|
+
@switch (currentView) {
|
|
17
|
+
@case (LOGIN_VIEWS.Credentials) {
|
|
18
|
+
<span class="{{ platformAnimationSrc ? '' : 'text-center' }}">
|
|
19
|
+
<h2
|
|
20
|
+
class="m-b-8"
|
|
21
|
+
translate
|
|
22
|
+
>
|
|
23
|
+
Welcome
|
|
24
|
+
</h2>
|
|
25
|
+
<p
|
|
26
|
+
class="text-16 m-b-40"
|
|
27
|
+
translate
|
|
28
|
+
>
|
|
29
|
+
Log in to access your IoT platform.
|
|
30
|
+
</p>
|
|
31
|
+
</span>
|
|
32
|
+
<c8y-credentials
|
|
33
|
+
(onChangeView)="handleLoginTemplate($event)"
|
|
34
|
+
[loginViewParams]="loginViewParams"
|
|
35
|
+
></c8y-credentials>
|
|
36
|
+
}
|
|
37
|
+
@case (LOGIN_VIEWS.RecoverPassword) {
|
|
38
|
+
<span class="{{ platformAnimationSrc ? '' : 'text-center' }}">
|
|
39
|
+
<h2
|
|
40
|
+
class="m-b-8"
|
|
41
|
+
translate
|
|
42
|
+
>
|
|
43
|
+
Reset password
|
|
44
|
+
</h2>
|
|
45
|
+
<p
|
|
46
|
+
class="text-16 m-b-40"
|
|
47
|
+
translate
|
|
48
|
+
>
|
|
49
|
+
Enter your email address and we'll send you a secure link to reset your password.
|
|
50
|
+
</p>
|
|
51
|
+
</span>
|
|
52
|
+
<c8y-recover-password
|
|
53
|
+
[recoverPasswordData]="recoverPasswordData"
|
|
54
|
+
(onChangeView)="handleLoginTemplate($event)"
|
|
55
|
+
></c8y-recover-password>
|
|
56
|
+
}
|
|
57
|
+
@case (LOGIN_VIEWS.ChangePassword) {
|
|
58
|
+
<c8y-change-password
|
|
59
|
+
(onChangeView)="handleLoginTemplate($event)"
|
|
60
|
+
[credentials]="credentials"
|
|
61
|
+
></c8y-change-password>
|
|
62
|
+
}
|
|
63
|
+
@case (LOGIN_VIEWS.TotpChallenge) {
|
|
64
|
+
<c8y-totp-auth
|
|
65
|
+
(onCancel)="reset(false)"
|
|
66
|
+
[view]="currentView"
|
|
67
|
+
[credentials]="credentials"
|
|
68
|
+
></c8y-totp-auth>
|
|
69
|
+
}
|
|
70
|
+
@case (LOGIN_VIEWS.TotpSetup) {
|
|
71
|
+
<c8y-totp-auth
|
|
72
|
+
(onCancel)="reset(false)"
|
|
73
|
+
[view]="currentView"
|
|
74
|
+
[credentials]="credentials"
|
|
75
|
+
></c8y-totp-auth>
|
|
76
|
+
}
|
|
77
|
+
@case (LOGIN_VIEWS.SmsChallenge) {
|
|
78
|
+
<c8y-sms-challenge
|
|
79
|
+
(onCancel)="reset(false)"
|
|
80
|
+
[credentials]="credentials"
|
|
81
|
+
></c8y-sms-challenge>
|
|
82
|
+
}
|
|
83
|
+
@case (LOGIN_VIEWS.ProvidePhoneNumber) {
|
|
84
|
+
<c8y-provide-phone-number
|
|
85
|
+
(onCancel)="reset(false)"
|
|
86
|
+
(onChangeView)="handleLoginTemplate($event)"
|
|
87
|
+
[credentials]="credentials"
|
|
88
|
+
></c8y-provide-phone-number>
|
|
89
|
+
}
|
|
90
|
+
@case (LOGIN_VIEWS.TenantIdSetup) {
|
|
91
|
+
<c8y-tenant-id-setup (onChangeView)="handleLoginTemplate($event)"></c8y-tenant-id-setup>
|
|
92
|
+
}
|
|
93
|
+
@case (LOGIN_VIEWS.MissingApplicationAccess) {
|
|
94
|
+
<c8y-missing-application-access></c8y-missing-application-access>
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
@if (!!(ui.state$ | async).loginExtraLink) {
|
|
98
|
+
<div class="text-center m-t-8">
|
|
99
|
+
@if (!!(ui.state$ | async).loginExtraLink.length) {
|
|
100
|
+
<div>
|
|
101
|
+
@for (link of (ui.state$ | async).loginExtraLink; track link) {
|
|
102
|
+
<a
|
|
103
|
+
class="small d-block m-t-8"
|
|
104
|
+
title="{{ link.label }}"
|
|
105
|
+
role="button"
|
|
106
|
+
[href]="link.url"
|
|
107
|
+
>
|
|
108
|
+
{{ link.label }}
|
|
109
|
+
</a>
|
|
110
|
+
}
|
|
111
|
+
</div>
|
|
112
|
+
} @else {
|
|
113
|
+
<a
|
|
114
|
+
class="small"
|
|
115
|
+
title="{{ (ui.state$ | async).loginExtraLink.label }}"
|
|
116
|
+
role="button"
|
|
117
|
+
[href]="(ui.state$ | async).loginExtraLink.url"
|
|
118
|
+
>
|
|
119
|
+
{{ (ui.state$ | async).loginExtraLink.label }}
|
|
120
|
+
</a>
|
|
121
|
+
}
|
|
122
|
+
</div>
|
|
123
|
+
}
|
|
124
|
+
</main>
|
|
125
|
+
</div>
|
|
126
|
+
}
|
|
128
127
|
</div>
|
|
@@ -20,7 +20,7 @@ import { gettext } from '@c8y/ngx-components/gettext';
|
|
|
20
20
|
import { LoginEvent, LoginViews, SsoData, SsoError } from './login.model';
|
|
21
21
|
import { CredentialsFromQueryParamsService } from './credentials-from-query-params.service';
|
|
22
22
|
import { CredentialsComponentParams } from './credentials-component-params';
|
|
23
|
-
import {
|
|
23
|
+
import { AsyncPipe } from '@angular/common';
|
|
24
24
|
import { CredentialsComponent } from './credentials/credentials.component';
|
|
25
25
|
import { RecoverPasswordComponent } from './recover-password/recover-password.component';
|
|
26
26
|
import { ChangePasswordComponent } from './change-password/change-password.component';
|
|
@@ -37,9 +37,6 @@ import { MissingApplicationAccessComponent } from './missing-application-access/
|
|
|
37
37
|
encapsulation: ViewEncapsulation.None,
|
|
38
38
|
standalone: true,
|
|
39
39
|
imports: [
|
|
40
|
-
NgIf,
|
|
41
|
-
NgSwitch,
|
|
42
|
-
NgSwitchCase,
|
|
43
40
|
CredentialsComponent,
|
|
44
41
|
RecoverPasswordComponent,
|
|
45
42
|
ChangePasswordComponent,
|
|
@@ -47,7 +44,6 @@ import { MissingApplicationAccessComponent } from './missing-application-access/
|
|
|
47
44
|
SmsChallengeComponent,
|
|
48
45
|
ProvidePhoneNumberComponent,
|
|
49
46
|
TenantIdSetupComponent,
|
|
50
|
-
NgFor,
|
|
51
47
|
AlertOutletComponent,
|
|
52
48
|
AsyncPipe,
|
|
53
49
|
MissingApplicationAccessComponent,
|
|
@@ -67,6 +63,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|
|
67
63
|
|
|
68
64
|
credentials: ICredentials = {};
|
|
69
65
|
loginViewParams: CredentialsComponentParams | { [key: string]: any } = {};
|
|
66
|
+
recoverPasswordData: LoginEvent['recoverPasswordData'];
|
|
70
67
|
displayAlerts = false;
|
|
71
68
|
private TOKEN_PARAM = 'token';
|
|
72
69
|
|
|
@@ -84,17 +81,17 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|
|
84
81
|
this.platformAnimationSrc = this.getPlatformAnimationPath();
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
ngOnInit() {
|
|
84
|
+
async ngOnInit() {
|
|
88
85
|
const token = this.getParamAndClear(this.TOKEN_PARAM);
|
|
89
86
|
const ssoData = this.getSsoData();
|
|
90
87
|
if (ssoData) {
|
|
91
|
-
this.handleSso(ssoData);
|
|
88
|
+
await this.handleSso(ssoData);
|
|
92
89
|
} else if (this.loginService.isFirstLogin) {
|
|
93
90
|
if (!token) {
|
|
94
91
|
this.loginAutomatically();
|
|
95
92
|
} else {
|
|
96
93
|
this.credentials.token = token;
|
|
97
|
-
this.reset(false);
|
|
94
|
+
await this.reset(false);
|
|
98
95
|
}
|
|
99
96
|
}
|
|
100
97
|
this.loginService.isFirstLogin = false;
|
|
@@ -109,6 +106,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|
|
109
106
|
this.currentView = event.view;
|
|
110
107
|
this.credentials = event.credentials || {};
|
|
111
108
|
this.loginViewParams = event.loginViewParams || {};
|
|
109
|
+
this.recoverPasswordData = event.recoverPasswordData || null;
|
|
112
110
|
}
|
|
113
111
|
|
|
114
112
|
@HostListener('keyup', ['$event']) onkeyup(event: KeyboardEvent) {
|
|
@@ -117,13 +115,13 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|
|
117
115
|
}
|
|
118
116
|
}
|
|
119
117
|
|
|
120
|
-
reset(missingPermissions: boolean) {
|
|
118
|
+
async reset(missingPermissions: boolean) {
|
|
121
119
|
if (missingPermissions) {
|
|
122
120
|
this.handleLoginTemplate({ view: LoginViews.MissingApplicationAccess });
|
|
123
121
|
return;
|
|
124
122
|
}
|
|
125
123
|
this.loginService.reset();
|
|
126
|
-
this.setView();
|
|
124
|
+
await this.setView();
|
|
127
125
|
this.loginService.cleanMessages();
|
|
128
126
|
}
|
|
129
127
|
|
|
@@ -163,14 +161,14 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|
|
163
161
|
if (result) {
|
|
164
162
|
return;
|
|
165
163
|
}
|
|
166
|
-
this.reset(true);
|
|
164
|
+
await this.reset(true);
|
|
167
165
|
} catch (e) {
|
|
168
166
|
await this.loginService.clearCookies();
|
|
169
167
|
const preferredLoginOptionType = this.loginService.loginMode.type;
|
|
170
168
|
if (preferredLoginOptionType === TenantLoginOptionType.OAUTH2 && e.res?.status !== 403) {
|
|
171
169
|
this.loginService.redirectToOauth();
|
|
172
170
|
} else {
|
|
173
|
-
this.reset(false);
|
|
171
|
+
await this.reset(false);
|
|
174
172
|
if (
|
|
175
173
|
preferredLoginOptionType === TenantLoginOptionType.OAUTH2_INTERNAL &&
|
|
176
174
|
window.location.protocol !== 'https:'
|
|
@@ -184,9 +182,25 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|
|
184
182
|
this.loginService.automaticLoginInProgress$.next(false);
|
|
185
183
|
}
|
|
186
184
|
|
|
187
|
-
private setView() {
|
|
185
|
+
private async setView() {
|
|
188
186
|
if (this.credentials && this.credentials.token) {
|
|
189
|
-
|
|
187
|
+
const email = this.options.get('email', '');
|
|
188
|
+
const tokenStatus = await this.loginService.validateResetToken(this.credentials.token, email);
|
|
189
|
+
if (tokenStatus === 'valid') {
|
|
190
|
+
this.handleLoginTemplate({
|
|
191
|
+
view: LoginViews.ChangePassword,
|
|
192
|
+
credentials: this.credentials
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
this.handleLoginTemplate({
|
|
196
|
+
view: LoginViews.RecoverPassword,
|
|
197
|
+
recoverPasswordData: {
|
|
198
|
+
email,
|
|
199
|
+
tokenStatus,
|
|
200
|
+
tenantId: this.loginService.getTenant()
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
190
204
|
} else if (this.loginService.showTenantSetup()) {
|
|
191
205
|
this.handleLoginTemplate({ view: LoginViews.TenantIdSetup });
|
|
192
206
|
} else {
|
|
@@ -217,12 +231,12 @@ export class LoginComponent implements OnInit, OnDestroy {
|
|
|
217
231
|
return false;
|
|
218
232
|
}
|
|
219
233
|
|
|
220
|
-
private handleSso(ssoData: SsoData | SsoError) {
|
|
234
|
+
private async handleSso(ssoData: SsoData | SsoError) {
|
|
221
235
|
if ('ssoError' in ssoData) {
|
|
222
236
|
this.loginService.showSsoError(
|
|
223
237
|
decodeURIComponent(ssoData.ssoErrorDescription).replace(/\+/g, '%20')
|
|
224
238
|
);
|
|
225
|
-
this.reset(false);
|
|
239
|
+
await this.reset(false);
|
|
226
240
|
} else {
|
|
227
241
|
this.loginService
|
|
228
242
|
.loginBySso(ssoData)
|
|
@@ -10,6 +10,11 @@ export interface LoginEvent {
|
|
|
10
10
|
view: LoginViews;
|
|
11
11
|
credentials?: ICredentials;
|
|
12
12
|
loginViewParams?: CredentialsComponentParams | { [key: string]: any };
|
|
13
|
+
recoverPasswordData?: {
|
|
14
|
+
email: string;
|
|
15
|
+
tokenStatus: 'valid' | 'invalid' | 'expired';
|
|
16
|
+
tenantId?: string;
|
|
17
|
+
};
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
export enum LoginViews {
|
|
@@ -21,7 +21,7 @@ import { switchMap } from 'rxjs/operators';
|
|
|
21
21
|
import { BehaviorSubject, EMPTY } from 'rxjs';
|
|
22
22
|
import { isEmpty } from 'lodash-es';
|
|
23
23
|
import { TranslateService } from '@ngx-translate/core';
|
|
24
|
-
import { SsoData } from './login.model';
|
|
24
|
+
import { LoginEvent, SsoData } from './login.model';
|
|
25
25
|
import {
|
|
26
26
|
getStoredToken,
|
|
27
27
|
getStoredTfaToken,
|
|
@@ -546,6 +546,28 @@ export class LoginService extends SimplifiedAuthService {
|
|
|
546
546
|
return await this.cookieAuth.logout({ redirect: 'manual' });
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Validates the reset password token.
|
|
551
|
+
* @param token The reset password token to validate.
|
|
552
|
+
* @param email The email address associated with the token.
|
|
553
|
+
* @returns Returns 'valid', 'invalid', or 'expired' based on the token status.
|
|
554
|
+
*/
|
|
555
|
+
async validateResetToken(
|
|
556
|
+
token: string,
|
|
557
|
+
email: string
|
|
558
|
+
): Promise<LoginEvent['recoverPasswordData']['tokenStatus']> {
|
|
559
|
+
try {
|
|
560
|
+
await this.user.validateResetToken(token, email);
|
|
561
|
+
return 'valid';
|
|
562
|
+
} catch (e) {
|
|
563
|
+
if (e.res?.status === 422) {
|
|
564
|
+
return 'expired';
|
|
565
|
+
} else {
|
|
566
|
+
return 'invalid';
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
549
571
|
/**
|
|
550
572
|
* Sets the tenant to the client and updates the credentials on the
|
|
551
573
|
* auth strategy.
|
|
@@ -1,49 +1,59 @@
|
|
|
1
|
-
<form
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
<form
|
|
2
|
+
class="loginForm"
|
|
3
|
+
#resetForm="ngForm"
|
|
4
|
+
(ngSubmit)="resetPassword()"
|
|
5
|
+
novalidate
|
|
6
|
+
>
|
|
7
|
+
@if (loginService.showTenant()) {
|
|
8
|
+
<c8y-form-group
|
|
9
|
+
class="tenantField"
|
|
10
|
+
id="tenantField"
|
|
11
|
+
>
|
|
12
|
+
<label translate>Tenant ID</label>
|
|
13
|
+
<input
|
|
14
|
+
class="form-control"
|
|
15
|
+
placeholder="{{ 'Tenant ID' | translate }}"
|
|
16
|
+
name="tenantId"
|
|
17
|
+
type="text"
|
|
18
|
+
required
|
|
19
|
+
[(ngModel)]="model.tenantId"
|
|
20
|
+
#tenantId="ngModel"
|
|
21
|
+
autocapitalize="off"
|
|
22
|
+
autocorrect="off"
|
|
23
|
+
/>
|
|
24
|
+
</c8y-form-group>
|
|
25
|
+
}
|
|
16
26
|
|
|
17
27
|
<c8y-form-group>
|
|
18
28
|
<label translate>Email address</label>
|
|
19
29
|
<input
|
|
30
|
+
class="form-control"
|
|
31
|
+
placeholder="{{ 'Email address' | translate }}"
|
|
32
|
+
name="email"
|
|
33
|
+
type="text"
|
|
34
|
+
required
|
|
20
35
|
[(ngModel)]="model.email"
|
|
21
36
|
#email="ngModel"
|
|
22
|
-
type="text"
|
|
23
|
-
name="email"
|
|
24
37
|
autocapitalize="off"
|
|
25
38
|
autocorrect="off"
|
|
26
|
-
class="form-control"
|
|
27
|
-
placeholder="{{ 'Email address' | translate }}"
|
|
28
39
|
email
|
|
29
|
-
required
|
|
30
40
|
/>
|
|
31
41
|
</c8y-form-group>
|
|
32
42
|
|
|
33
43
|
<div class="m-t-32">
|
|
34
44
|
<button
|
|
45
|
+
class="btn btn-primary btn-lg btn-block form-group"
|
|
35
46
|
title="{{ 'Reset password' | translate }}"
|
|
36
|
-
[disabled]="!resetForm.form.valid || isLoading"
|
|
37
47
|
type="submit"
|
|
38
|
-
|
|
48
|
+
[disabled]="!resetForm.form.valid || isLoading"
|
|
39
49
|
>
|
|
40
50
|
{{ 'Reset password' | translate }}
|
|
41
51
|
</button>
|
|
42
52
|
<div class="text-center m-t-8">
|
|
43
53
|
<button
|
|
44
|
-
type="submit"
|
|
45
|
-
title="{{ 'Login' | translate }}"
|
|
46
54
|
class="btn btn-link btn-sm"
|
|
55
|
+
title="{{ 'Login' | translate }}"
|
|
56
|
+
type="submit"
|
|
47
57
|
(click)="onChangeView.emit({ view: LOGIN_VIEWS.Credentials })"
|
|
48
58
|
>
|
|
49
59
|
{{ 'Login' | translate }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
|
|
1
|
+
import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
|
|
2
2
|
import { UserService } from '@c8y/client';
|
|
3
3
|
import { LoginService } from '../login.service';
|
|
4
4
|
import { LoginEvent, LoginViews } from '../login.model';
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
C8yTranslateDirective,
|
|
8
8
|
FormGroupComponent,
|
|
9
9
|
RequiredInputPlaceholderDirective,
|
|
10
|
-
C8yTranslatePipe
|
|
10
|
+
C8yTranslatePipe,
|
|
11
|
+
AlertService
|
|
11
12
|
} from '@c8y/ngx-components';
|
|
12
|
-
|
|
13
|
-
import { NgIf } from '@angular/common';
|
|
13
|
+
import { gettext } from '@c8y/ngx-components/gettext';
|
|
14
14
|
|
|
15
15
|
@Component({
|
|
16
16
|
selector: 'c8y-recover-password',
|
|
@@ -20,13 +20,13 @@ import { NgIf } from '@angular/common';
|
|
|
20
20
|
imports: [
|
|
21
21
|
FormsModule,
|
|
22
22
|
C8yTranslateDirective,
|
|
23
|
-
NgIf,
|
|
24
23
|
FormGroupComponent,
|
|
25
24
|
RequiredInputPlaceholderDirective,
|
|
26
25
|
C8yTranslatePipe
|
|
27
26
|
]
|
|
28
27
|
})
|
|
29
28
|
export class RecoverPasswordComponent implements OnInit {
|
|
29
|
+
@Input() recoverPasswordData: LoginEvent['recoverPasswordData'];
|
|
30
30
|
@Output() onChangeView = new EventEmitter<LoginEvent>();
|
|
31
31
|
LOGIN_VIEWS = LoginViews;
|
|
32
32
|
isLoading = false;
|
|
@@ -37,11 +37,27 @@ export class RecoverPasswordComponent implements OnInit {
|
|
|
37
37
|
|
|
38
38
|
constructor(
|
|
39
39
|
private users: UserService,
|
|
40
|
-
public loginService: LoginService
|
|
40
|
+
public loginService: LoginService,
|
|
41
|
+
private alertService: AlertService
|
|
41
42
|
) {}
|
|
42
43
|
|
|
43
44
|
ngOnInit() {
|
|
44
|
-
|
|
45
|
+
if (this.recoverPasswordData) {
|
|
46
|
+
this.model.email = this.recoverPasswordData.email || '';
|
|
47
|
+
this.model.tenantId = this.recoverPasswordData.tenantId || '';
|
|
48
|
+
|
|
49
|
+
const message =
|
|
50
|
+
this.recoverPasswordData.tokenStatus === 'expired'
|
|
51
|
+
? gettext('This password reset link expired. To continue, request a new one.')
|
|
52
|
+
: this.recoverPasswordData.tokenStatus === 'invalid'
|
|
53
|
+
? gettext('This password reset link is invalid. To continue, request a new one.')
|
|
54
|
+
: '';
|
|
55
|
+
if (message) {
|
|
56
|
+
this.alertService.danger(message);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
this.model.tenantId = this.loginService.getTenant();
|
|
60
|
+
}
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
async resetPassword() {
|