@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.
Files changed (40) hide show
  1. package/.browserslistrc +16 -0
  2. package/cumulocity.config.ts +27 -0
  3. package/jest.config.js +18 -0
  4. package/package.json +23 -0
  5. package/public/favicon.ico +0 -0
  6. package/public/platform-animation.svg +2533 -0
  7. package/src/app/app.config.ts +11 -0
  8. package/src/app/bootstrap-login/bootstrap-login.component.html +3 -0
  9. package/src/app/bootstrap-login/bootstrap-login.component.ts +16 -0
  10. package/src/app/login/change-password/change-password.component.html +97 -0
  11. package/src/app/login/change-password/change-password.component.ts +101 -0
  12. package/src/app/login/credentials/credentials.component.html +141 -0
  13. package/src/app/login/credentials/credentials.component.ts +148 -0
  14. package/src/app/login/credentials-component-params.ts +4 -0
  15. package/src/app/login/credentials-from-query-params.service.ts +86 -0
  16. package/src/app/login/index.ts +9 -0
  17. package/src/app/login/login.component.html +128 -0
  18. package/src/app/login/login.component.less +136 -0
  19. package/src/app/login/login.component.ts +238 -0
  20. package/src/app/login/login.model.ts +36 -0
  21. package/src/app/login/login.service.ts +651 -0
  22. package/src/app/login/missing-application-access/missing-application-access.component.html +2 -0
  23. package/src/app/login/missing-application-access/missing-application-access.component.ts +21 -0
  24. package/src/app/login/password-strength-validator.directive.ts +26 -0
  25. package/src/app/login/provide-phone-number/provide-phone-number.component.html +39 -0
  26. package/src/app/login/provide-phone-number/provide-phone-number.component.ts +73 -0
  27. package/src/app/login/recover-password/recover-password.component.html +53 -0
  28. package/src/app/login/recover-password/recover-password.component.ts +59 -0
  29. package/src/app/login/sms-challenge/sms-challenge.component.html +50 -0
  30. package/src/app/login/sms-challenge/sms-challenge.component.ts +134 -0
  31. package/src/app/login/strength-validator-service.ts +18 -0
  32. package/src/app/login/tenant-id-setup/tenant-id-setup.component.html +28 -0
  33. package/src/app/login/tenant-id-setup/tenant-id-setup.component.ts +94 -0
  34. package/src/app/login/totp-auth/totp-auth.component.html +18 -0
  35. package/src/app/login/totp-auth/totp-auth.component.ts +72 -0
  36. package/src/bootstrap.ts +19 -0
  37. package/src/i18n.ts +18 -0
  38. package/src/main.ts +25 -0
  39. package/src/polyfills.ts +33 -0
  40. package/tsconfig.app.json +20 -0
@@ -0,0 +1,39 @@
1
+ <form #twoFactorForm="ngForm" class="loginForm" (ngSubmit)="save()" novalidate>
2
+ <div class="legend form-block center" translate>Two-factor authentication</div>
3
+
4
+ <c8y-form-group [ngClass]="requestInProgress || twoFactorForm.invalid ? 'p-b-8' : ''">
5
+ <label translate>Provide your phone number</label>
6
+
7
+ <input
8
+ class="form-control"
9
+ [(ngModel)]="phoneNumber"
10
+ #contactPhone="ngModel"
11
+ type="text"
12
+ name="phone"
13
+ autocomplete="off"
14
+ placeholder="{{ 'e.g. +49 9 876 543 210`LOCALIZE`' | translate }}"
15
+ c8yPhoneValidation
16
+ required
17
+ />
18
+ </c8y-form-group>
19
+
20
+ <button
21
+ title="{{ 'Save and continue' | translate }}"
22
+ type="submit"
23
+ class="btn btn-primary btn-lg btn-block form-group"
24
+ [disabled]="requestInProgress || twoFactorForm.invalid"
25
+ >
26
+ {{ 'Save and continue' | translate }}
27
+ </button>
28
+
29
+ <div class="d-flex m-t-8">
30
+ <a
31
+ title="{{ 'Login' | translate }}"
32
+ class="small pointer m-auto"
33
+ href="#"
34
+ (click)="onCancel.emit()"
35
+ >
36
+ {{ 'Login' | translate }}
37
+ </a>
38
+ </div>
39
+ </form>
@@ -0,0 +1,73 @@
1
+ import { Component, Output, EventEmitter, Input } from '@angular/core';
2
+ import { LoginService } from '../login.service';
3
+ import { LoginEvent, LoginViews } from '../login.model';
4
+ import { ICredentials, UserService } from '@c8y/client';
5
+ import { FormsModule } from '@angular/forms';
6
+ import { NgClass } from '@angular/common';
7
+ import {
8
+ RequiredInputPlaceholderDirective,
9
+ PhoneValidationDirective,
10
+ C8yTranslatePipe,
11
+ FormGroupComponent,
12
+ C8yTranslateDirective,
13
+ AlertService
14
+ } from '@c8y/ngx-components';
15
+
16
+ @Component({
17
+ selector: 'c8y-provide-phone-number',
18
+ templateUrl: './provide-phone-number.component.html',
19
+ standalone: true,
20
+ imports: [
21
+ FormsModule,
22
+ C8yTranslateDirective,
23
+ FormGroupComponent,
24
+ NgClass,
25
+ RequiredInputPlaceholderDirective,
26
+ PhoneValidationDirective,
27
+ C8yTranslatePipe
28
+ ]
29
+ })
30
+ export class ProvidePhoneNumberComponent {
31
+ @Input() credentials: ICredentials;
32
+ @Output() onCancel = new EventEmitter();
33
+ @Output() onChangeView = new EventEmitter<LoginEvent>();
34
+
35
+ phoneNumber: string;
36
+ requestInProgress = false;
37
+ private readonly sendTfa: string = '0';
38
+
39
+ constructor(
40
+ public loginService: LoginService,
41
+ public alert: AlertService,
42
+ private userService: UserService
43
+ ) {}
44
+
45
+ async save() {
46
+ try {
47
+ this.requestInProgress = true;
48
+ await this.userService.savePhoneNumber(this.phoneNumber);
49
+ await this.sendTFASms();
50
+ this.onChangeView.emit({
51
+ view: LoginViews.SmsChallenge,
52
+ credentials: this.credentials
53
+ });
54
+ } catch (e) {
55
+ this.alert.addServerFailure(e);
56
+ } finally {
57
+ this.requestInProgress = false;
58
+ }
59
+ }
60
+
61
+ private async sendTFASms() {
62
+ try {
63
+ await this.userService.verifyTFACode(this.sendTfa);
64
+ } catch (e) {
65
+ if (e.res.status === 403) {
66
+ this.loginService.cleanMessages();
67
+ this.loginService.addSuccessMessage('send_sms');
68
+ } else {
69
+ throw e;
70
+ }
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,53 @@
1
+ <form #resetForm="ngForm" class="loginForm" (ngSubmit)="resetPassword()" novalidate>
2
+ <c8y-form-group class="tenantField" id="tenantField" *ngIf="loginService.showTenant()">
3
+ <label translate>Tenant ID</label>
4
+ <input
5
+ [(ngModel)]="model.tenantId"
6
+ #tenantId="ngModel"
7
+ type="text"
8
+ name="tenantId"
9
+ autocapitalize="off"
10
+ autocorrect="off"
11
+ class="form-control"
12
+ placeholder="{{ 'Tenant ID' | translate }}"
13
+ required
14
+ />
15
+ </c8y-form-group>
16
+
17
+ <c8y-form-group>
18
+ <label translate>Email address</label>
19
+ <input
20
+ [(ngModel)]="model.email"
21
+ #email="ngModel"
22
+ type="text"
23
+ name="email"
24
+ autocapitalize="off"
25
+ autocorrect="off"
26
+ class="form-control"
27
+ placeholder="{{ 'Email address' | translate }}"
28
+ email
29
+ required
30
+ />
31
+ </c8y-form-group>
32
+
33
+ <div class="m-t-32">
34
+ <button
35
+ title="{{ 'Reset password' | translate }}"
36
+ [disabled]="!resetForm.form.valid || isLoading"
37
+ type="submit"
38
+ class="btn btn-primary btn-lg btn-block form-group"
39
+ >
40
+ {{ 'Reset password' | translate }}
41
+ </button>
42
+ <div class="text-center m-t-8">
43
+ <button
44
+ type="submit"
45
+ title="{{ 'Login' | translate }}"
46
+ class="btn btn-link btn-sm"
47
+ (click)="onChangeView.emit({ view: LOGIN_VIEWS.Credentials })"
48
+ >
49
+ {{ 'Login' | translate }}
50
+ </button>
51
+ </div>
52
+ </div>
53
+ </form>
@@ -0,0 +1,59 @@
1
+ import { Component, OnInit, Output, EventEmitter } from '@angular/core';
2
+ import { UserService } from '@c8y/client';
3
+ import { LoginService } from '../login.service';
4
+ import { LoginEvent, LoginViews } from '../login.model';
5
+ import { FormsModule } from '@angular/forms';
6
+ import {
7
+ C8yTranslateDirective,
8
+ FormGroupComponent,
9
+ RequiredInputPlaceholderDirective,
10
+ C8yTranslatePipe
11
+ } from '@c8y/ngx-components';
12
+
13
+ import { NgIf } from '@angular/common';
14
+
15
+ @Component({
16
+ selector: 'c8y-recover-password',
17
+ templateUrl: './recover-password.component.html',
18
+ styles: [],
19
+ standalone: true,
20
+ imports: [
21
+ FormsModule,
22
+ C8yTranslateDirective,
23
+ NgIf,
24
+ FormGroupComponent,
25
+ RequiredInputPlaceholderDirective,
26
+ C8yTranslatePipe
27
+ ]
28
+ })
29
+ export class RecoverPasswordComponent implements OnInit {
30
+ @Output() onChangeView = new EventEmitter<LoginEvent>();
31
+ LOGIN_VIEWS = LoginViews;
32
+ isLoading = false;
33
+ model = {
34
+ email: '',
35
+ tenantId: ''
36
+ };
37
+
38
+ constructor(
39
+ private users: UserService,
40
+ public loginService: LoginService
41
+ ) {}
42
+
43
+ ngOnInit() {
44
+ this.model.tenantId = this.loginService.getTenant();
45
+ }
46
+
47
+ async resetPassword() {
48
+ try {
49
+ this.isLoading = true;
50
+ const { res } = await this.users.sendPasswordResetMail(this.model.email, this.model.tenantId);
51
+ if (res.status === 200) {
52
+ this.loginService.addSuccessMessage('password_reset_requested');
53
+ }
54
+ } finally {
55
+ this.loginService.reset();
56
+ this.isLoading = false;
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,50 @@
1
+ <form #twoFactorForm="ngForm" class="loginForm" (ngSubmit)="verifyTFACode()" novalidate>
2
+ <div class="legend form-block center" translate>Two-factor authentication</div>
3
+
4
+ <c8y-form-group>
5
+ <label translate>Verification code</label>
6
+ <input
7
+ [(ngModel)]="model.smsToken"
8
+ #sms_token="ngModel"
9
+ type="text"
10
+ name="sms_token"
11
+ autofocus
12
+ autocapitalize="off"
13
+ autocorrect="off"
14
+ class="form-control"
15
+ placeholder="{{ 'e.g.' | translate }} 624327"
16
+ required
17
+ />
18
+ <p *ngIf="!twoFactorForm.form.valid || isLoading" class="help-block" translate>
19
+ Insert the code received via SMS.
20
+ </p>
21
+ </c8y-form-group>
22
+
23
+ <button
24
+ title="{{ 'Verify' | translate }}"
25
+ [disabled]="!twoFactorForm.form.valid || isLoading"
26
+ class="btn btn-primary btn-lg btn-block form-group"
27
+ >
28
+ {{ 'Verify' | translate }}
29
+ </button>
30
+
31
+ <div class="d-flex m-t-8 j-c-center">
32
+ <button
33
+ type="button"
34
+ title="{{ 'Send new code' | translate }}"
35
+ [ngClass]="{ disabled: isLoading }"
36
+ class="btn btn-link btn-sm"
37
+ (click)="resendTFASms()"
38
+ >
39
+ {{ 'Send new code' | translate }}
40
+ </button>
41
+ <button
42
+ type="button"
43
+ title="{{ 'Log in' | translate }}"
44
+ class="btn btn-link btn-sm"
45
+ (click)="onCancel.emit()"
46
+ >
47
+ {{ 'Log in' | translate }}
48
+ </button>
49
+ </div>
50
+ </form>
@@ -0,0 +1,134 @@
1
+ import { Component, Output, EventEmitter, Input } from '@angular/core';
2
+ import { UserService, ICredentials } from '@c8y/client';
3
+ import { LoginService } from '../login.service';
4
+ import {
5
+ AlertService,
6
+ C8yTranslateDirective,
7
+ FormGroupComponent,
8
+ RequiredInputPlaceholderDirective,
9
+ C8yTranslatePipe,
10
+ AppStateService
11
+ } from '@c8y/ngx-components';
12
+ import { gettext } from '@c8y/ngx-components/gettext';
13
+ import { FormsModule } from '@angular/forms';
14
+ import { NgIf, NgClass } from '@angular/common';
15
+ import { LoginEvent, LoginViews } from '../login.model';
16
+
17
+ @Component({
18
+ selector: 'c8y-sms-challenge',
19
+ templateUrl: './sms-challenge.component.html',
20
+ styles: [],
21
+ standalone: true,
22
+ imports: [
23
+ FormsModule,
24
+ C8yTranslateDirective,
25
+ FormGroupComponent,
26
+ RequiredInputPlaceholderDirective,
27
+ NgIf,
28
+ NgClass,
29
+ C8yTranslatePipe
30
+ ]
31
+ })
32
+ export class SmsChallengeComponent {
33
+ @Input() credentials: ICredentials;
34
+ @Output() onCancel = new EventEmitter();
35
+ @Output() onChangeView = new EventEmitter<LoginEvent>();
36
+
37
+ model = {
38
+ smsToken: ''
39
+ };
40
+ isLoading = false;
41
+
42
+ private resendTfa = '0';
43
+
44
+ constructor(
45
+ public loginService: LoginService,
46
+ private users: UserService,
47
+ private alert: AlertService,
48
+ private appState: AppStateService
49
+ ) {}
50
+
51
+ async verifyTFACode() {
52
+ this.isLoading = true;
53
+ if (await this.usesOAuthInternal()) {
54
+ await this.verifyCodeWithOauth();
55
+ } else {
56
+ await this.verifyCodeWithBasicAuth();
57
+ }
58
+ this.isLoading = false;
59
+ }
60
+
61
+ async resendTFASms() {
62
+ try {
63
+ this.isLoading = true;
64
+ await this.users.verifyTFACode(this.resendTfa);
65
+ } catch (e) {
66
+ if (e.res.status === 403) {
67
+ this.loginService.cleanMessages();
68
+ this.loginService.addSuccessMessage('resend_sms');
69
+ } else {
70
+ this.alert.addServerFailure(e);
71
+ }
72
+ } finally {
73
+ this.isLoading = false;
74
+ }
75
+ }
76
+
77
+ private async usesOAuthInternal() {
78
+ return this.loginService.isPasswordGrantLogin(this.credentials);
79
+ }
80
+
81
+ private async verifyCodeWithOauth() {
82
+ try {
83
+ const { credentials } = this;
84
+ await this.loginService.switchLoginMode({ ...credentials, tfa: this.model.smsToken });
85
+ await this.loginService.authFulfilled();
86
+ const result = await this.loginService.ensureUserPermissionsForRedirect(
87
+ this.appState.currentUser.value
88
+ );
89
+ if (!result) {
90
+ this.onChangeView.emit({ view: LoginViews.MissingApplicationAccess });
91
+ }
92
+ } catch (e) {
93
+ const resStatus = e.res && e.res.status;
94
+ if (resStatus === 401) {
95
+ // it is assumed that the user and password are correct so it must be the tfa code
96
+ this.alert.danger(gettext('Invalid code'));
97
+ } else {
98
+ this.alert.addServerFailure(e);
99
+ }
100
+ }
101
+ }
102
+
103
+ private async verifyCodeWithBasicAuth() {
104
+ try {
105
+ const { res } = await this.users.verifyTFACode(this.model.smsToken);
106
+ const tfaToken = res.headers.get('tfatoken');
107
+ this.credentials.tfa = tfaToken;
108
+ await this.loginWithTFA(tfaToken);
109
+ } catch (e) {
110
+ const resStatus = e.res && e.res.status;
111
+ // BE returns 403 in case of invalid tfa code
112
+ if (resStatus === 403) {
113
+ this.alert.danger(gettext('Invalid code'));
114
+ } else {
115
+ this.alert.addServerFailure(e);
116
+ }
117
+ }
118
+ }
119
+
120
+ private async loginWithTFA(tfaToken) {
121
+ try {
122
+ await this.loginService.login(
123
+ this.loginService.useBasicAuth({ tfa: tfaToken }),
124
+ this.credentials
125
+ );
126
+ this.loginService.saveTFAToken(tfaToken, sessionStorage);
127
+ if (this.loginService.rememberMe) {
128
+ this.loginService.saveTFAToken(tfaToken, localStorage);
129
+ }
130
+ } catch (e) {
131
+ this.alert.addServerFailure(e);
132
+ }
133
+ }
134
+ }
@@ -0,0 +1,18 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { PasswordService } from '@c8y/ngx-components';
3
+ import { PasswordStrength } from '@c8y/client';
4
+
5
+ @Injectable({
6
+ providedIn: 'root'
7
+ })
8
+ export class StrengthValidatorService {
9
+ constructor(private passwordService: PasswordService) {}
10
+
11
+ isStrong(password: string): boolean {
12
+ return this.isPasswordGreen(this.passwordService.getStrengthColor(password).passwordStrength);
13
+ }
14
+
15
+ private isPasswordGreen(strength: PasswordStrength) {
16
+ return (strength as PasswordStrength) === (PasswordStrength.GREEN as PasswordStrength);
17
+ }
18
+ }
@@ -0,0 +1,28 @@
1
+ <form #tenantIdSetupForm="ngForm" class="loginForm" (ngSubmit)="setupLoginMode()" novalidate>
2
+ <div class="legend form-block center" translate>Tenant setup</div>
3
+ <c8y-form-group class="tenantField" id="tenantField">
4
+ <label for="tenant" translate>Tenant ID</label>
5
+ <input
6
+ [(ngModel)]="model.tenant"
7
+ #tenant="ngModel"
8
+ type="text"
9
+ name="tenant"
10
+ id="tenant"
11
+ autocapitalize="off"
12
+ autocorrect="off"
13
+ class="form-control"
14
+ placeholder="{{ 'e.g.' | translate }} t12345"
15
+ placeholder-no-required-hint
16
+ required
17
+ />
18
+ </c8y-form-group>
19
+
20
+ <button
21
+ title="{{ 'Apply' | translate }}"
22
+ type="submit"
23
+ class="btn btn-primary btn-lg btn-block form-group"
24
+ [disabled]="!tenantIdSetupForm.form.valid"
25
+ >
26
+ {{ 'Apply' | translate }}
27
+ </button>
28
+ </form>
@@ -0,0 +1,94 @@
1
+ import { Component, Output, EventEmitter } from '@angular/core';
2
+ import { LoginEvent, LoginViews } from '../login.model';
3
+ import { FetchClient } from '@c8y/client';
4
+ import {
5
+ AppStateService,
6
+ AlertService,
7
+ C8yTranslateDirective,
8
+ FormGroupComponent,
9
+ RequiredInputPlaceholderDirective,
10
+ C8yTranslatePipe
11
+ } from '@c8y/ngx-components';
12
+ import { LoginService } from '../login.service';
13
+ import { TranslateService } from '@ngx-translate/core';
14
+ import { gettext } from '@c8y/ngx-components/gettext';
15
+ import { FormsModule } from '@angular/forms';
16
+
17
+ @Component({
18
+ selector: 'c8y-tenant-id-setup',
19
+ templateUrl: './tenant-id-setup.component.html',
20
+ styles: [],
21
+ standalone: true,
22
+ imports: [
23
+ FormsModule,
24
+ C8yTranslateDirective,
25
+ FormGroupComponent,
26
+ RequiredInputPlaceholderDirective,
27
+ C8yTranslatePipe
28
+ ]
29
+ })
30
+
31
+ /**
32
+ * `TenantIdSetupComponent` is intended to be shown when tenant's id cannot be determined based on the current URL.
33
+ * It asks the user to provide target tenant's id and then it fetches login options for this tenant.
34
+ * In case of OAI-Secure login mode, login options will contain `domain` property set by backend.
35
+ * The component will redirect user to this domain, preserving URL path and params.
36
+ */
37
+ export class TenantIdSetupComponent {
38
+ @Output() onChangeView = new EventEmitter<LoginEvent>();
39
+ LOGIN_VIEWS = LoginViews;
40
+ model = {
41
+ tenant: ''
42
+ };
43
+
44
+ constructor(
45
+ private client: FetchClient,
46
+ private ui: AppStateService,
47
+ private loginService: LoginService,
48
+ private alert: AlertService,
49
+ private translateService: TranslateService
50
+ ) {}
51
+
52
+ /**
53
+ * Sets up login mode for particular tenant. In case of OAI-Secure will redirect user to tenant domain.
54
+ */
55
+ async setupLoginMode() {
56
+ this.client.tenant = this.model.tenant;
57
+ try {
58
+ await this.ui.refreshLoginOptions();
59
+ this.loginService.initLoginOptions();
60
+ this.redirectToCorrectDomain();
61
+ } catch (e) {
62
+ if (e.res && e.res.status === 401) {
63
+ this.alert.danger(
64
+ this.translateService.instant(
65
+ gettext('Could not find tenant with ID "{{ tenantId }}".'),
66
+ { tenantId: this.model.tenant }
67
+ )
68
+ );
69
+ } else {
70
+ this.alert.addServerFailure(e);
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Redirects to tenant domain when login mode contains domain.
77
+ */
78
+ redirectToCorrectDomain() {
79
+ const loginRedirectDomain = this.loginService.loginMode.loginRedirectDomain;
80
+ if (loginRedirectDomain) {
81
+ const alreadyOnCorrectDomain = window.location.href.includes(loginRedirectDomain);
82
+ if (!alreadyOnCorrectDomain) {
83
+ this.loginService.redirectToDomain(loginRedirectDomain);
84
+ } else {
85
+ this.onChangeView.emit({
86
+ view: LoginViews.Credentials,
87
+ loginViewParams: { showTenant: true, disableTenant: true }
88
+ });
89
+ }
90
+ } else {
91
+ this.onChangeView.emit({ view: LoginViews.Credentials });
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,18 @@
1
+ <div
2
+ class="legend form-block center"
3
+ translate
4
+ >
5
+ Two-factor authentication
6
+ </div>
7
+
8
+ <c8y-totp-setup *ngIf="isSetup">
9
+ </c8y-totp-setup>
10
+ <c8y-totp-challenge
11
+ [isModal]="false"
12
+ [loading]="loading"
13
+ [hasError]="hasError"
14
+ [verify]="view === LOGIN_VIEWS.TotpSetup"
15
+ (onSuccess)="onTotpSuccess($event)"
16
+ (totpUnconfirmedEmitter)="onCancel.emit()"
17
+ ></c8y-totp-challenge>
18
+
@@ -0,0 +1,72 @@
1
+ import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
2
+ import { ICredentials, UserService } from '@c8y/client';
3
+ import {
4
+ AlertService,
5
+ C8yTranslateDirective,
6
+ TotpSetupComponent,
7
+ TotpChallengeComponent,
8
+ AppStateService
9
+ } from '@c8y/ngx-components';
10
+ import { LoginService } from '../login.service';
11
+ import { LoginEvent, LoginViews } from '../login.model';
12
+ import { gettext } from '@c8y/ngx-components/gettext';
13
+ import { NgIf } from '@angular/common';
14
+
15
+ @Component({
16
+ selector: 'c8y-totp-auth',
17
+ templateUrl: './totp-auth.component.html',
18
+ standalone: true,
19
+ imports: [C8yTranslateDirective, NgIf, TotpSetupComponent, TotpChallengeComponent]
20
+ })
21
+ export class TotpAuthComponent implements OnInit {
22
+ @Input() credentials: ICredentials;
23
+ @Input() view: LoginViews;
24
+ @Output() onCancel = new EventEmitter();
25
+ @Output() onChangeView = new EventEmitter<LoginEvent>();
26
+ LOGIN_VIEWS = LoginViews;
27
+ loading = false;
28
+ hasError = false;
29
+ isSetup = false;
30
+
31
+ constructor(
32
+ public loginService: LoginService,
33
+ private userService: UserService,
34
+ private alert: AlertService,
35
+ private appState: AppStateService
36
+ ) {}
37
+
38
+ ngOnInit() {
39
+ if (this.view === this.LOGIN_VIEWS.TotpSetup) {
40
+ this.isSetup = true;
41
+ }
42
+ }
43
+
44
+ async onTotpSuccess(code: string) {
45
+ try {
46
+ this.loading = true;
47
+ this.hasError = false;
48
+ this.credentials.tfa = code;
49
+ if (this.isSetup) {
50
+ await this.userService.activateTotp();
51
+ }
52
+ await this.loginService.switchLoginMode(this.credentials);
53
+ await this.loginService.authFulfilled();
54
+ const result = await this.loginService.ensureUserPermissionsForRedirect(
55
+ this.appState.currentUser.value
56
+ );
57
+ if (!result) {
58
+ this.onChangeView.emit({ view: LoginViews.MissingApplicationAccess });
59
+ }
60
+ } catch (e) {
61
+ this.alert.removeLastDanger();
62
+ if (e.data && e.data.message === 'Authentication failed! : User account is locked') {
63
+ this.alert.warning(gettext('Authentication failed due to: user account is locked.'));
64
+ } else {
65
+ this.alert.addServerFailure(e);
66
+ this.hasError = true;
67
+ }
68
+ } finally {
69
+ this.loading = false;
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,19 @@
1
+ import './polyfills';
2
+ import '@angular/compiler';
3
+
4
+ import { enableProdMode } from '@angular/core';
5
+ import { bootstrapApplication } from '@angular/platform-browser';
6
+ import { appConfig } from './app/app.config';
7
+ import { BootstrapLoginComponent } from './app/bootstrap-login/bootstrap-login.component';
8
+ import { provideBootstrapMetadata } from '@c8y/ngx-components';
9
+ import { BootstrapMetaData } from '@c8y/bootstrap';
10
+
11
+ declare const __MODE__: string;
12
+ if (__MODE__ === 'production') {
13
+ enableProdMode();
14
+ }
15
+
16
+ export function bootstrap(metadata: BootstrapMetaData) {
17
+ appConfig.providers.push(...provideBootstrapMetadata(metadata));
18
+ return bootstrapApplication(BootstrapLoginComponent, appConfig).catch(err => console.log(err));
19
+ }
package/src/i18n.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Internationalizing files in po format (https://en.wikipedia.org/wiki/Gettext#Translating)
3
+ * You can always add additional strings by adding your own po file. All po files are
4
+ * combined to one JSON file per language and are loaded if the specific language is needed.
5
+ */
6
+ import '@c8y/ngx-components/locales/de.po';
7
+ import '@c8y/ngx-components/locales/en.po';
8
+ import '@c8y/ngx-components/locales/en_US.po';
9
+ import '@c8y/ngx-components/locales/es.po';
10
+ import '@c8y/ngx-components/locales/fr.po';
11
+ import '@c8y/ngx-components/locales/ja_JP.po';
12
+ import '@c8y/ngx-components/locales/ko.po';
13
+ import '@c8y/ngx-components/locales/nl.po';
14
+ import '@c8y/ngx-components/locales/pl.po';
15
+ import '@c8y/ngx-components/locales/pt_BR.po';
16
+ import '@c8y/ngx-components/locales/zh_CN.po';
17
+ import '@c8y/ngx-components/locales/zh_TW.po';
18
+ // import './locales/de.po'; // <- adding additional strings to the german translation.