@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,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,3 @@
1
+ <c8y-login></c8y-login>
2
+
3
+ <c8y-cookie-banner></c8y-cookie-banner>
@@ -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,4 @@
1
+ export interface CredentialsComponentParams {
2
+ showTenant: boolean;
3
+ disableTenant: boolean;
4
+ }
@@ -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';