@c8y/login 1022.3.2
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/.browserslistrc +16 -0
- package/cumulocity.config.ts +27 -0
- package/jest.config.js +18 -0
- package/package.json +23 -0
- package/public/favicon.ico +0 -0
- package/public/platform-animation.svg +2533 -0
- package/src/app/app.config.ts +11 -0
- package/src/app/bootstrap-login/bootstrap-login.component.html +3 -0
- package/src/app/bootstrap-login/bootstrap-login.component.ts +16 -0
- package/src/app/login/change-password/change-password.component.html +97 -0
- package/src/app/login/change-password/change-password.component.ts +101 -0
- package/src/app/login/credentials/credentials.component.html +141 -0
- package/src/app/login/credentials/credentials.component.ts +148 -0
- package/src/app/login/credentials-component-params.ts +4 -0
- package/src/app/login/credentials-from-query-params.service.ts +86 -0
- package/src/app/login/index.ts +9 -0
- package/src/app/login/login.component.html +128 -0
- package/src/app/login/login.component.less +136 -0
- package/src/app/login/login.component.ts +238 -0
- package/src/app/login/login.model.ts +36 -0
- package/src/app/login/login.service.ts +651 -0
- package/src/app/login/missing-application-access/missing-application-access.component.html +2 -0
- package/src/app/login/missing-application-access/missing-application-access.component.ts +21 -0
- package/src/app/login/password-strength-validator.directive.ts +26 -0
- package/src/app/login/provide-phone-number/provide-phone-number.component.html +39 -0
- package/src/app/login/provide-phone-number/provide-phone-number.component.ts +73 -0
- package/src/app/login/recover-password/recover-password.component.html +53 -0
- package/src/app/login/recover-password/recover-password.component.ts +59 -0
- package/src/app/login/sms-challenge/sms-challenge.component.html +50 -0
- package/src/app/login/sms-challenge/sms-challenge.component.ts +134 -0
- package/src/app/login/strength-validator-service.ts +18 -0
- package/src/app/login/tenant-id-setup/tenant-id-setup.component.html +28 -0
- package/src/app/login/tenant-id-setup/tenant-id-setup.component.ts +94 -0
- package/src/app/login/totp-auth/totp-auth.component.html +18 -0
- package/src/app/login/totp-auth/totp-auth.component.ts +72 -0
- package/src/bootstrap.ts +19 -0
- package/src/i18n.ts +18 -0
- package/src/main.ts +25 -0
- package/src/polyfills.ts +33 -0
- package/tsconfig.app.json +20 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
|
|
2
|
+
import { provideAnimations } from '@angular/platform-browser/animations';
|
|
3
|
+
import { CoreModule, RouterModule } from '@c8y/ngx-components';
|
|
4
|
+
|
|
5
|
+
export const appConfig: ApplicationConfig = {
|
|
6
|
+
providers: [
|
|
7
|
+
provideAnimations(),
|
|
8
|
+
importProvidersFrom(RouterModule.forRoot()),
|
|
9
|
+
importProvidersFrom(CoreModule.forRoot())
|
|
10
|
+
]
|
|
11
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { CookieBannerComponent, TranslationLoaderService } from '@c8y/ngx-components';
|
|
3
|
+
import { LoginComponent } from '../login';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'c8y-bootstrap',
|
|
7
|
+
templateUrl: './bootstrap-login.component.html',
|
|
8
|
+
standalone: true,
|
|
9
|
+
imports: [LoginComponent, CookieBannerComponent]
|
|
10
|
+
})
|
|
11
|
+
export class BootstrapLoginComponent {
|
|
12
|
+
constructor(
|
|
13
|
+
// only here to ensure the service is instantiated
|
|
14
|
+
public translationLoaderService: TranslationLoaderService
|
|
15
|
+
) {}
|
|
16
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<form class="loginForm" (ngSubmit)="changePassword()" #changePasswordForm="ngForm" novalidate>
|
|
2
|
+
<div class="legend form-block center" translate>Change password</div>
|
|
3
|
+
|
|
4
|
+
<c8y-form-group class="tenantField" id="tenantField" *ngIf="loginService.showTenant()">
|
|
5
|
+
<label translate>Tenant ID</label>
|
|
6
|
+
<input
|
|
7
|
+
[(ngModel)]="model.tenantId"
|
|
8
|
+
#tenantId="ngModel"
|
|
9
|
+
type="text"
|
|
10
|
+
name="tenantId"
|
|
11
|
+
autocapitalize="off"
|
|
12
|
+
autocorrect="off"
|
|
13
|
+
class="form-control"
|
|
14
|
+
placeholder="{{ 'Tenant ID' | translate }}"
|
|
15
|
+
required
|
|
16
|
+
/>
|
|
17
|
+
</c8y-form-group>
|
|
18
|
+
|
|
19
|
+
<c8y-form-group>
|
|
20
|
+
<label translate>Email address</label>
|
|
21
|
+
<input
|
|
22
|
+
[(ngModel)]="model.email"
|
|
23
|
+
#email="ngModel"
|
|
24
|
+
type="text"
|
|
25
|
+
name="email"
|
|
26
|
+
autocapitalize="off"
|
|
27
|
+
autocorrect="off"
|
|
28
|
+
class="form-control"
|
|
29
|
+
placeholder="{{ 'Email address' | translate }}"
|
|
30
|
+
email
|
|
31
|
+
required
|
|
32
|
+
[readonly]="emailReadOnly"
|
|
33
|
+
/>
|
|
34
|
+
</c8y-form-group>
|
|
35
|
+
|
|
36
|
+
<div class="row content-flex-50">
|
|
37
|
+
<div class="col-6">
|
|
38
|
+
<c8y-form-group>
|
|
39
|
+
<label translate>New password</label>
|
|
40
|
+
<input
|
|
41
|
+
[(ngModel)]="model.newPassword"
|
|
42
|
+
#newPassword="ngModel"
|
|
43
|
+
type="password"
|
|
44
|
+
name="newPassword"
|
|
45
|
+
class="form-control"
|
|
46
|
+
placeholder="{{ 'New password' | translate }}"
|
|
47
|
+
[pattern]="passwordPattern"
|
|
48
|
+
autocomplete="new-password"
|
|
49
|
+
[passwordStrengthEnforced]="passwordStrengthEnforced"
|
|
50
|
+
required
|
|
51
|
+
/>
|
|
52
|
+
<c8y-messages>
|
|
53
|
+
<c8y-message
|
|
54
|
+
name="pattern"
|
|
55
|
+
[text]="loginService.ERROR_MESSAGES.pattern_newPassword"
|
|
56
|
+
></c8y-message>
|
|
57
|
+
</c8y-messages>
|
|
58
|
+
</c8y-form-group>
|
|
59
|
+
|
|
60
|
+
<c8y-form-group>
|
|
61
|
+
<label translate>Confirm password</label>
|
|
62
|
+
<input
|
|
63
|
+
[(ngModel)]="model.newPasswordConfirm"
|
|
64
|
+
#newPasswordConfirm="ngModel"
|
|
65
|
+
type="password"
|
|
66
|
+
name="newPasswordConfirm"
|
|
67
|
+
class="form-control"
|
|
68
|
+
placeholder="{{ 'Confirm password' | translate }}"
|
|
69
|
+
passwordConfirm="newPassword"
|
|
70
|
+
autocomplete="new-password"
|
|
71
|
+
required
|
|
72
|
+
/>
|
|
73
|
+
<c8y-messages>
|
|
74
|
+
<c8y-message
|
|
75
|
+
name="passwordConfirm"
|
|
76
|
+
[text]="loginService.ERROR_MESSAGES.passwordConfirm"
|
|
77
|
+
></c8y-message>
|
|
78
|
+
</c8y-messages>
|
|
79
|
+
</c8y-form-group>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="col-6">
|
|
82
|
+
<c8y-password-check-list
|
|
83
|
+
[password]="model.newPassword"
|
|
84
|
+
[strengthEnforced]="passwordStrengthEnforced"
|
|
85
|
+
></c8y-password-check-list>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<button
|
|
90
|
+
title="{{ 'Set password' | translate }}"
|
|
91
|
+
[disabled]="!changePasswordForm.form.valid || isLoading"
|
|
92
|
+
type="submit"
|
|
93
|
+
class="btn btn-primary btn-lg btn-block form-group"
|
|
94
|
+
>
|
|
95
|
+
{{ 'Set password' | translate }}
|
|
96
|
+
</button>
|
|
97
|
+
</form>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Component, OnInit, Output, Input, EventEmitter } from '@angular/core';
|
|
2
|
+
import { LoginService } from '../login.service';
|
|
3
|
+
import { IResetPassword, ICredentials, UserService, PasswordStrength } from '@c8y/client';
|
|
4
|
+
import { LoginEvent, LoginViews } from '../login.model';
|
|
5
|
+
import { FormsModule } from '@angular/forms';
|
|
6
|
+
import { NgIf } from '@angular/common';
|
|
7
|
+
import { PasswordStrengthValidatorDirective } from '../password-strength-validator.directive';
|
|
8
|
+
import {
|
|
9
|
+
C8yTranslatePipe,
|
|
10
|
+
PasswordCheckListComponent,
|
|
11
|
+
PasswordConfirm,
|
|
12
|
+
MessageDirective,
|
|
13
|
+
MessagesComponent,
|
|
14
|
+
RequiredInputPlaceholderDirective,
|
|
15
|
+
FormGroupComponent,
|
|
16
|
+
C8yTranslateDirective,
|
|
17
|
+
AlertService,
|
|
18
|
+
OptionsService
|
|
19
|
+
} from '@c8y/ngx-components';
|
|
20
|
+
import { PasswordStrengthService } from '@c8y/ngx-components';
|
|
21
|
+
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'c8y-change-password',
|
|
24
|
+
templateUrl: './change-password.component.html',
|
|
25
|
+
styles: [],
|
|
26
|
+
standalone: true,
|
|
27
|
+
imports: [
|
|
28
|
+
FormsModule,
|
|
29
|
+
C8yTranslateDirective,
|
|
30
|
+
NgIf,
|
|
31
|
+
FormGroupComponent,
|
|
32
|
+
RequiredInputPlaceholderDirective,
|
|
33
|
+
PasswordStrengthValidatorDirective,
|
|
34
|
+
MessagesComponent,
|
|
35
|
+
MessageDirective,
|
|
36
|
+
PasswordConfirm,
|
|
37
|
+
PasswordCheckListComponent,
|
|
38
|
+
C8yTranslatePipe
|
|
39
|
+
]
|
|
40
|
+
})
|
|
41
|
+
export class ChangePasswordComponent implements OnInit {
|
|
42
|
+
@Input() credentials: ICredentials;
|
|
43
|
+
@Output() onChangeView = new EventEmitter<LoginEvent>();
|
|
44
|
+
|
|
45
|
+
passwordPattern = /^[a-zA-Z0-9`~!@#$%^&*()_|+\-=?;:'",.<>{}[\]\\/]{8,32}$/;
|
|
46
|
+
isLoading = false;
|
|
47
|
+
model = {
|
|
48
|
+
tenantId: '',
|
|
49
|
+
email: '',
|
|
50
|
+
newPassword: '',
|
|
51
|
+
newPasswordConfirm: ''
|
|
52
|
+
};
|
|
53
|
+
emailReadOnly = false;
|
|
54
|
+
passwordStrengthEnforced = false;
|
|
55
|
+
|
|
56
|
+
private TOKEN_PARAM = 'token';
|
|
57
|
+
private EMAIL_PARAM = 'email';
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
public loginService: LoginService,
|
|
61
|
+
private passwordStrength: PasswordStrengthService,
|
|
62
|
+
private users: UserService,
|
|
63
|
+
private options: OptionsService,
|
|
64
|
+
private alert: AlertService
|
|
65
|
+
) {}
|
|
66
|
+
|
|
67
|
+
async ngOnInit() {
|
|
68
|
+
this.model.tenantId = this.loginService.getTenant();
|
|
69
|
+
this.model.email = this.options.get(this.EMAIL_PARAM, '');
|
|
70
|
+
this.emailReadOnly = !!this.model.email;
|
|
71
|
+
this.passwordStrengthEnforced = await this.passwordStrength.getPasswordStrengthEnforced();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async changePassword() {
|
|
75
|
+
const resetPassword: IResetPassword = {
|
|
76
|
+
token: this.credentials.token,
|
|
77
|
+
email: this.model.email,
|
|
78
|
+
newPassword: this.model.newPassword,
|
|
79
|
+
passwordStrength: PasswordStrength.GREEN // @TODO: MTM-58234 - Deprecated - currently Backend requires this parameter.
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
this.isLoading = true;
|
|
83
|
+
const { res } = await this.users.resetPassword(resetPassword, this.model.tenantId);
|
|
84
|
+
if (res.status === 200) {
|
|
85
|
+
this.loginService.addSuccessMessage('password_changed');
|
|
86
|
+
this.credentials.token = undefined;
|
|
87
|
+
this.options.set(this.TOKEN_PARAM, undefined);
|
|
88
|
+
if (this.loginService.showTenantSetup()) {
|
|
89
|
+
this.onChangeView.emit({ view: LoginViews.TenantIdSetup });
|
|
90
|
+
} else {
|
|
91
|
+
this.onChangeView.emit({ view: LoginViews.Credentials });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
this.alert.addServerFailure(e);
|
|
96
|
+
} finally {
|
|
97
|
+
this.loginService.reset();
|
|
98
|
+
this.isLoading = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<div
|
|
2
|
+
id="oauth"
|
|
3
|
+
class="m-b-40"
|
|
4
|
+
*ngIf="oauthOptions.initRequest && oauthOptions.visibleOnLoginPage"
|
|
5
|
+
>
|
|
6
|
+
<button
|
|
7
|
+
class="btn btn-default btn-block btn-lg form-group m-t-8"
|
|
8
|
+
title="{{ oauthOptions.buttonName | translate }}"
|
|
9
|
+
type="button"
|
|
10
|
+
(click)="redirectToOauth()"
|
|
11
|
+
>
|
|
12
|
+
<i
|
|
13
|
+
class="pull-left"
|
|
14
|
+
[c8yIcon]="'sign-in'"
|
|
15
|
+
></i>
|
|
16
|
+
{{ oauthOptions.buttonName | translate }}
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<form
|
|
21
|
+
class="loginForm"
|
|
22
|
+
(ngSubmit)="login()"
|
|
23
|
+
#loginForm="ngForm"
|
|
24
|
+
*ngIf="showLoginForm"
|
|
25
|
+
novalidate
|
|
26
|
+
>
|
|
27
|
+
<span
|
|
28
|
+
class="legend form-block center"
|
|
29
|
+
*ngIf="!(oauthOptions.initRequest && oauthOptions.visibleOnLoginPage); else orLegend"
|
|
30
|
+
translate
|
|
31
|
+
>
|
|
32
|
+
Login
|
|
33
|
+
</span>
|
|
34
|
+
|
|
35
|
+
<ng-template #orLegend>
|
|
36
|
+
<div
|
|
37
|
+
class="legend form-block center"
|
|
38
|
+
translate
|
|
39
|
+
>
|
|
40
|
+
or enter your credentials`prefixed by login button`
|
|
41
|
+
</div>
|
|
42
|
+
</ng-template>
|
|
43
|
+
|
|
44
|
+
<c8y-form-group
|
|
45
|
+
class="tenantField m-b-40"
|
|
46
|
+
id="tenantField"
|
|
47
|
+
*ngIf="showTenant"
|
|
48
|
+
>
|
|
49
|
+
<label
|
|
50
|
+
for="tenant"
|
|
51
|
+
translate
|
|
52
|
+
>
|
|
53
|
+
Tenant ID
|
|
54
|
+
</label>
|
|
55
|
+
<input
|
|
56
|
+
class="form-control"
|
|
57
|
+
id="tenant"
|
|
58
|
+
placeholder="{{ 'e.g.' | translate }} t12345"
|
|
59
|
+
name="tenant"
|
|
60
|
+
type="text"
|
|
61
|
+
required
|
|
62
|
+
[(ngModel)]="model.tenant"
|
|
63
|
+
#tenant="ngModel"
|
|
64
|
+
autocapitalize="off"
|
|
65
|
+
autocorrect="off"
|
|
66
|
+
placeholder-no-required-hint
|
|
67
|
+
[readonly]="loginViewParams.disableTenant"
|
|
68
|
+
/>
|
|
69
|
+
</c8y-form-group>
|
|
70
|
+
<c8y-form-group class="m-b-40">
|
|
71
|
+
<label
|
|
72
|
+
for="user"
|
|
73
|
+
translate
|
|
74
|
+
>
|
|
75
|
+
Username
|
|
76
|
+
</label>
|
|
77
|
+
<input
|
|
78
|
+
class="form-control"
|
|
79
|
+
id="user"
|
|
80
|
+
placeholder="{{ 'e.g. joe or joe.doe@example.com`LOCALIZE`' | translate }}"
|
|
81
|
+
name="user"
|
|
82
|
+
type="text"
|
|
83
|
+
required
|
|
84
|
+
[(ngModel)]="model.user"
|
|
85
|
+
#user="ngModel"
|
|
86
|
+
autocapitalize="off"
|
|
87
|
+
autocorrect="off"
|
|
88
|
+
placeholder-no-required-hint
|
|
89
|
+
/>
|
|
90
|
+
</c8y-form-group>
|
|
91
|
+
<c8y-form-group class="m-b-40">
|
|
92
|
+
<label
|
|
93
|
+
for="password"
|
|
94
|
+
translate
|
|
95
|
+
>
|
|
96
|
+
Password
|
|
97
|
+
</label>
|
|
98
|
+
<c8y-password-input
|
|
99
|
+
name="password"
|
|
100
|
+
required
|
|
101
|
+
[id]="'password'"
|
|
102
|
+
[(ngModel)]="model.password"
|
|
103
|
+
[autocomplete]="'off'"
|
|
104
|
+
></c8y-password-input>
|
|
105
|
+
</c8y-form-group>
|
|
106
|
+
<div
|
|
107
|
+
class="form-group "
|
|
108
|
+
*ngIf="showBasicAuth"
|
|
109
|
+
>
|
|
110
|
+
<label
|
|
111
|
+
class="c8y-checkbox"
|
|
112
|
+
title="{{ 'Remember me' | translate }}"
|
|
113
|
+
>
|
|
114
|
+
<input
|
|
115
|
+
name="remember"
|
|
116
|
+
type="checkbox"
|
|
117
|
+
[(ngModel)]="loginService.rememberMe"
|
|
118
|
+
/>
|
|
119
|
+
<span></span>
|
|
120
|
+
<span>{{ 'Remember me' | translate }}</span>
|
|
121
|
+
</label>
|
|
122
|
+
</div>
|
|
123
|
+
<button
|
|
124
|
+
class="btn btn-primary btn-lg btn-block form-group"
|
|
125
|
+
title="{{ 'Log in' | translate }}"
|
|
126
|
+
type="submit"
|
|
127
|
+
[disabled]="!loginForm.form.valid || isLoading"
|
|
128
|
+
>
|
|
129
|
+
{{ 'Log in' | translate }}
|
|
130
|
+
</button>
|
|
131
|
+
<div class="text-center m-t-8">
|
|
132
|
+
<button
|
|
133
|
+
class="btn btn-link btn-sm"
|
|
134
|
+
title="{{ 'Forgot password?' | translate }}"
|
|
135
|
+
type="button"
|
|
136
|
+
(click)="onChangeView.emit({ view: LOGIN_VIEWS.RecoverPassword })"
|
|
137
|
+
>
|
|
138
|
+
{{ 'Forgot password?' | translate }}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
</form>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Component, OnInit, Output, Input, EventEmitter } from '@angular/core';
|
|
2
|
+
import { LoginService } from '../login.service';
|
|
3
|
+
import { ICredentials } from '@c8y/client';
|
|
4
|
+
import {
|
|
5
|
+
AlertService,
|
|
6
|
+
IconDirective,
|
|
7
|
+
C8yTranslateDirective,
|
|
8
|
+
FormGroupComponent,
|
|
9
|
+
RequiredInputPlaceholderDirective,
|
|
10
|
+
PasswordInputComponent,
|
|
11
|
+
C8yTranslatePipe
|
|
12
|
+
} from '@c8y/ngx-components';
|
|
13
|
+
import { gettext } from '@c8y/ngx-components/gettext';
|
|
14
|
+
import { LoginEvent, LoginViews } from '../login.model';
|
|
15
|
+
import { CredentialsFromQueryParamsService } from '../credentials-from-query-params.service';
|
|
16
|
+
import { CredentialsComponentParams } from '../credentials-component-params';
|
|
17
|
+
import { NgIf } from '@angular/common';
|
|
18
|
+
import { FormsModule } from '@angular/forms';
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'c8y-credentials',
|
|
22
|
+
templateUrl: './credentials.component.html',
|
|
23
|
+
styles: [],
|
|
24
|
+
standalone: true,
|
|
25
|
+
imports: [
|
|
26
|
+
NgIf,
|
|
27
|
+
IconDirective,
|
|
28
|
+
FormsModule,
|
|
29
|
+
C8yTranslateDirective,
|
|
30
|
+
FormGroupComponent,
|
|
31
|
+
RequiredInputPlaceholderDirective,
|
|
32
|
+
PasswordInputComponent,
|
|
33
|
+
C8yTranslatePipe
|
|
34
|
+
]
|
|
35
|
+
})
|
|
36
|
+
export class CredentialsComponent implements OnInit {
|
|
37
|
+
@Output() onChangeView = new EventEmitter<LoginEvent>();
|
|
38
|
+
|
|
39
|
+
@Input() loginViewParams: CredentialsComponentParams = {
|
|
40
|
+
disableTenant: false,
|
|
41
|
+
showTenant: false
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
LOGIN_VIEWS = LoginViews;
|
|
45
|
+
model: ICredentials = {};
|
|
46
|
+
isLoading = false;
|
|
47
|
+
showLoginForm = false;
|
|
48
|
+
showBasicAuth = false;
|
|
49
|
+
oauthOptions: any = {};
|
|
50
|
+
showTenant = false;
|
|
51
|
+
|
|
52
|
+
private readonly PASSWORD_RESET_HEADER_NAME = 'passwordresettoken';
|
|
53
|
+
private readonly NO_PHONE_HEADER_NAME = 'NoPhoneHeader';
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
public loginService: LoginService,
|
|
57
|
+
public alert: AlertService,
|
|
58
|
+
private credentialsFromQueryParamsService: CredentialsFromQueryParamsService
|
|
59
|
+
) {}
|
|
60
|
+
|
|
61
|
+
ngOnInit() {
|
|
62
|
+
const { oauthOptions, loginMode } = this.loginService;
|
|
63
|
+
this.model.tenant = this.loginService.getTenant();
|
|
64
|
+
this.showLoginForm =
|
|
65
|
+
typeof loginMode.visibleOnLoginPage === 'undefined' || loginMode.visibleOnLoginPage;
|
|
66
|
+
this.showBasicAuth = loginMode.type === 'BASIC';
|
|
67
|
+
this.oauthOptions = oauthOptions;
|
|
68
|
+
const credentialsFromQueryParams =
|
|
69
|
+
this.credentialsFromQueryParamsService.getCredentialsFromQueryParams();
|
|
70
|
+
Object.assign(this.model, credentialsFromQueryParams);
|
|
71
|
+
this.showTenant = this.loginViewParams.showTenant || this.loginService.showTenant();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
redirectToOauth() {
|
|
75
|
+
this.loginService.redirectToOauth();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Allows to login into the application using basic auth.
|
|
80
|
+
* If successful logged in the client is set in shared/cumulocity.service.ts
|
|
81
|
+
*/
|
|
82
|
+
async login() {
|
|
83
|
+
try {
|
|
84
|
+
this.isLoading = true;
|
|
85
|
+
const basicAuth = this.loginService.useBasicAuth(this.model);
|
|
86
|
+
const hasPermission = await this.loginService.login(basicAuth, this.model);
|
|
87
|
+
if (!hasPermission) {
|
|
88
|
+
this.onChangeView.emit({ view: LoginViews.MissingApplicationAccess });
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (e.res && e.res.headers && e.res.headers.get(this.PASSWORD_RESET_HEADER_NAME)) {
|
|
92
|
+
this.handlePasswordReset(e.res);
|
|
93
|
+
} else if (e.res && e.res.status === 401 && /pin.*generated/i.test(e.data.message)) {
|
|
94
|
+
this.handleSmsChallenge(e.data.message);
|
|
95
|
+
} else if (e.res && e.res.status === 401 && /TOTP/i.test(e.data.message)) {
|
|
96
|
+
this.handleTotpChallenge(e.data.message);
|
|
97
|
+
} else if (
|
|
98
|
+
e.res &&
|
|
99
|
+
e.res.headers &&
|
|
100
|
+
e.res.headers.get(this.NO_PHONE_HEADER_NAME) &&
|
|
101
|
+
!this.loginService.isSupportUser(this.model)
|
|
102
|
+
) {
|
|
103
|
+
this.handleNoPhoneNumberProvided();
|
|
104
|
+
} else {
|
|
105
|
+
this.loginService.generateOauthToken(this.model);
|
|
106
|
+
this.loginService.reset();
|
|
107
|
+
this.alert.addServerFailure(e);
|
|
108
|
+
}
|
|
109
|
+
} finally {
|
|
110
|
+
this.isLoading = false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private handlePasswordReset(e: any) {
|
|
115
|
+
this.alert.removeLastDanger();
|
|
116
|
+
this.model.token = e.headers.get(this.PASSWORD_RESET_HEADER_NAME);
|
|
117
|
+
this.onChangeView.emit({ view: LoginViews.ChangePassword, credentials: this.model });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private handleTotpChallenge(message) {
|
|
121
|
+
if (/TOTP setup required/i.test(message)) {
|
|
122
|
+
this.onChangeView.emit({ view: LoginViews.TotpSetup, credentials: this.model });
|
|
123
|
+
} else {
|
|
124
|
+
this.onChangeView.emit({ view: LoginViews.TotpChallenge, credentials: this.model });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private handleSmsChallenge(message: string) {
|
|
129
|
+
if (/pin has already been generated/i.test(message)) {
|
|
130
|
+
this.alert.warning(
|
|
131
|
+
gettext(
|
|
132
|
+
'The verification code was already sent. For a new verification code, please click on the link above.'
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
this.alert.removeLastDanger();
|
|
137
|
+
this.onChangeView.emit({ view: LoginViews.SmsChallenge, credentials: this.model });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private handleNoPhoneNumberProvided() {
|
|
141
|
+
this.onChangeView.emit({ view: LoginViews.ProvidePhoneNumber, credentials: this.model });
|
|
142
|
+
this.alert.warning(
|
|
143
|
+
gettext(
|
|
144
|
+
'Two-factor authentication has been turned on for this account. Provide your phone number above to save it in your user profile and start receiving verification codes via SMS.'
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { ICredentials } from '@c8y/client';
|
|
3
|
+
|
|
4
|
+
@Injectable({ providedIn: 'root' })
|
|
5
|
+
export class CredentialsFromQueryParamsService {
|
|
6
|
+
private readonly queryParamsToHandle: Array<keyof ICredentials> = ['tenant', 'user'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Retrieves any subset of credentials provided via queryParams
|
|
10
|
+
* @return ICredentials found in queryParams.
|
|
11
|
+
*/
|
|
12
|
+
getCredentialsFromQueryParams(): ICredentials {
|
|
13
|
+
const credentials: ICredentials = {};
|
|
14
|
+
try {
|
|
15
|
+
const params = new URLSearchParams(window.location.search);
|
|
16
|
+
this.queryParamsToHandle.forEach(param => {
|
|
17
|
+
const value = this.getParameterFromQueryParams(params, param);
|
|
18
|
+
if (value) {
|
|
19
|
+
credentials[param] = value;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// URLSearchParams probably not available in all browsers (https://caniuse.com/urlsearchparams)
|
|
24
|
+
}
|
|
25
|
+
return credentials;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Removes credentials from the queryParameters if any are present.
|
|
30
|
+
* In case some credentials were present, this method will cause a page reload.
|
|
31
|
+
* @return boolean if credentials were found.
|
|
32
|
+
*/
|
|
33
|
+
removeCredentialsFromQueryParams(): boolean {
|
|
34
|
+
try {
|
|
35
|
+
const params = new URLSearchParams(window.location.search);
|
|
36
|
+
const hasRemovedAtLeastOneParam = this.queryParamsToHandle
|
|
37
|
+
.map(param => this.removeParameterFromQueryParameters(params, param))
|
|
38
|
+
.reduceRight((prev, curr) => prev || curr, false);
|
|
39
|
+
if (hasRemovedAtLeastOneParam) {
|
|
40
|
+
window.location.search = params.toString();
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// URLSearchParams probably not available in all browsers (https://caniuse.com/urlsearchparams)
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Looks for the specified key in the provided URLSearchParams.
|
|
51
|
+
* If the specified key was found, it will be removed.
|
|
52
|
+
* @return boolean if key was found.
|
|
53
|
+
*/
|
|
54
|
+
private removeParameterFromQueryParameters(
|
|
55
|
+
params: URLSearchParams,
|
|
56
|
+
key: keyof ICredentials
|
|
57
|
+
): boolean {
|
|
58
|
+
const keyAsString = `${key}`;
|
|
59
|
+
if (!params.has(keyAsString)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
params.delete(keyAsString);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Looks for the specified key in the provided URLSearchParams.
|
|
68
|
+
* If the specified key was found, it's value will be returned.
|
|
69
|
+
* Otherwise null will be returned.
|
|
70
|
+
* @return string/null.
|
|
71
|
+
*/
|
|
72
|
+
private getParameterFromQueryParams(
|
|
73
|
+
params: URLSearchParams,
|
|
74
|
+
key: keyof ICredentials
|
|
75
|
+
): string | null {
|
|
76
|
+
const keyAsString = `${key}`;
|
|
77
|
+
if (!params.has(keyAsString)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const value = params.get(keyAsString);
|
|
81
|
+
if (!value) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './login.service';
|
|
2
|
+
export * from './login.model';
|
|
3
|
+
export * from './login.component';
|
|
4
|
+
export * from './password-strength-validator.directive';
|
|
5
|
+
export * from './strength-validator-service';
|
|
6
|
+
export * from './recover-password/recover-password.component';
|
|
7
|
+
export * from './change-password/change-password.component';
|
|
8
|
+
export * from './totp-auth/totp-auth.component';
|
|
9
|
+
export * from './credentials/credentials.component';
|