summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form')
-rwxr-xr-xsrc/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.html95
-rwxr-xr-xsrc/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.scss68
-rwxr-xr-xsrc/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.spec.ts77
-rwxr-xr-xsrc/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.ts51
4 files changed, 291 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.html
new file mode 100755
index 000000000..2dc30df52
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.html
@@ -0,0 +1,95 @@
+<div>
+ <h2 i18n>Please set a new password.</h2>
+ <h4 i18n>You will be redirected to the login page afterwards.</h4>
+ <form #frm="ngForm"
+ [formGroup]="userForm"
+ novalidate>
+
+ <!-- Old password -->
+ <div class="form-group has-feedback">
+ <div class="input-group">
+ <input class="form-control"
+ type="password"
+ placeholder="Old password..."
+ id="oldpassword"
+ formControlName="oldpassword"
+ autocomplete="new-password"
+ autofocus>
+ <span class="input-group-append">
+ <button class="btn btn-outline-light btn-password"
+ cdPasswordButton="oldpassword">
+ </button>
+ </span>
+ </div>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('oldpassword', frm, 'required')"
+ i18n>This field is required.</span>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('oldpassword', frm, 'notmatch')"
+ i18n>The old and new passwords must be different.</span>
+ </div>
+
+ <!-- New password -->
+ <div class="form-group has-feedback">
+ <div class="input-group">
+ <input class="form-control"
+ type="password"
+ placeholder="New password..."
+ id="newpassword"
+ autocomplete="new-password"
+ formControlName="newpassword">
+ <span class="input-group-append">
+ <button type="button"
+ class="btn btn-outline-light btn-password"
+ cdPasswordButton="newpassword">
+ </button>
+ </span>
+ </div>
+ <div class="password-strength-level">
+ <div class="{{ passwordStrengthLevelClass }}"
+ data-toggle="tooltip"
+ title="{{ passwordValuation }}">
+ </div>
+ </div>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('newpassword', frm, 'required')"
+ i18n>This field is required.</span>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('newpassword', frm, 'notmatch')"
+ i18n>The old and new passwords must be different.</span>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('newpassword', frm, 'passwordPolicy')">
+ {{ passwordValuation }}
+ </span>
+ </div>
+
+ <!-- Confirm new password -->
+ <div class="form-group has-feedback">
+ <div class="input-group">
+ <input class="form-control"
+ type="password"
+ autocomplete="new-password"
+ placeholder="Confirm new password..."
+ id="confirmnewpassword"
+ formControlName="confirmnewpassword">
+ <span class="input-group-append">
+ <button class="btn btn-outline-light btn-password"
+ cdPasswordButton="confirmnewpassword">
+ </button>
+ </span>
+ </div>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('confirmnewpassword', frm, 'required')"
+ i18n>This field is required.</span>
+ <span class="invalid-feedback"
+ *ngIf="userForm.showError('confirmnewpassword', frm, 'match')"
+ i18n>Password confirmation doesn't match the new password.</span>
+ </div>
+ <cd-form-button-panel (submitActionEvent)="onSubmit()"
+ (backActionEvent)="onCancel()"
+ [form]="userForm"
+ [disabled]="userForm.invalid"
+ [submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
+ wrappingClass="text-right"></cd-form-button-panel>
+ </form>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.scss
new file mode 100755
index 000000000..15addd1e8
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.scss
@@ -0,0 +1,68 @@
+@use 'sass:map';
+@use './src/styles/vendor/variables' as vv;
+
+$dark-secondary: darken(vv.$secondary, 4%);
+
+::ng-deep cd-login-password-form {
+ h4 {
+ margin: 0 0 30px;
+ }
+
+ .form-group {
+ background-color: $dark-secondary;
+ border-left: 4px solid vv.$white;
+
+ &:focus-within {
+ border-left: 4px solid map.get(vv.$theme-colors, 'accent');
+ }
+ }
+
+ .btn-password,
+ .btn-password:focus,
+ .form-control,
+ .form-control:focus {
+ background-color: $dark-secondary;
+ border: 0;
+ box-shadow: none;
+ color: vv.$body-color-bright;
+ filter: none;
+ outline: none;
+ }
+
+ .form-control::placeholder {
+ color: vv.$gray-600;
+ }
+
+ .btn-password:focus {
+ outline-color: vv.$primary;
+ }
+
+ button.btn:not(:first-child) {
+ margin-left: 5px;
+ }
+}
+
+// This will override the colors applied by chrome
+@keyframes autofill {
+ to {
+ background-color: $dark-secondary;
+ color: vv.$body-color-bright;
+ }
+}
+
+input:-webkit-autofill {
+ animation-fill-mode: both;
+ animation-name: autofill;
+ border-radius: 0;
+ box-shadow: 0 0 0 1000px $dark-secondary inset;
+ -webkit-text-fill-color: vv.$body-color-bright;
+ transition-property: none;
+}
+
+.invalid-feedback {
+ padding-left: 9px;
+}
+
+.is-invalid.cd-form-control {
+ border-color: transparent;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.spec.ts
new file mode 100755
index 000000000..062d076e4
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.spec.ts
@@ -0,0 +1,77 @@
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { ToastrModule } from 'ngx-toastr';
+
+import { AuthService } from '~/app/shared/api/auth.service';
+import { ComponentsModule } from '~/app/shared/components/components.module';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { SharedModule } from '~/app/shared/shared.module';
+import { configureTestBed, FormHelper } from '~/testing/unit-test-helper';
+import { LoginPasswordFormComponent } from './login-password-form.component';
+
+describe('LoginPasswordFormComponent', () => {
+ let component: LoginPasswordFormComponent;
+ let fixture: ComponentFixture<LoginPasswordFormComponent>;
+ let form: CdFormGroup;
+ let formHelper: FormHelper;
+ let httpTesting: HttpTestingController;
+ let router: Router;
+ let authStorageService: AuthStorageService;
+ let authService: AuthService;
+
+ configureTestBed({
+ imports: [
+ HttpClientTestingModule,
+ RouterTestingModule,
+ ReactiveFormsModule,
+ ComponentsModule,
+ ToastrModule.forRoot(),
+ SharedModule
+ ],
+ declarations: [LoginPasswordFormComponent]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LoginPasswordFormComponent);
+ component = fixture.componentInstance;
+ httpTesting = TestBed.inject(HttpTestingController);
+ router = TestBed.inject(Router);
+ authStorageService = TestBed.inject(AuthStorageService);
+ authService = TestBed.inject(AuthService);
+ spyOn(router, 'navigate');
+ fixture.detectChanges();
+ form = component.userForm;
+ formHelper = new FormHelper(form);
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should submit', () => {
+ spyOn(component, 'onPasswordChange').and.callThrough();
+ spyOn(authService, 'logout');
+ spyOn(authStorageService, 'getUsername').and.returnValue('test1');
+ formHelper.setMultipleValues({
+ oldpassword: 'foo',
+ newpassword: 'bar'
+ });
+ formHelper.setValue('confirmnewpassword', 'bar', true);
+ component.onSubmit();
+ const request = httpTesting.expectOne('api/user/test1/change_password');
+ request.flush({});
+ expect(component.onPasswordChange).toHaveBeenCalled();
+ expect(authService.logout).toHaveBeenCalled();
+ });
+
+ it('should cancel', () => {
+ spyOn(authService, 'logout');
+ component.onCancel();
+ expect(authService.logout).toHaveBeenCalled();
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.ts
new file mode 100755
index 000000000..0e72cca35
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/login-password-form/login-password-form.component.ts
@@ -0,0 +1,51 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { AuthService } from '~/app/shared/api/auth.service';
+import { UserService } from '~/app/shared/api/user.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { PasswordPolicyService } from '~/app/shared/services/password-policy.service';
+import { UserPasswordFormComponent } from '../user-password-form/user-password-form.component';
+
+@Component({
+ selector: 'cd-login-password-form',
+ templateUrl: './login-password-form.component.html',
+ styleUrls: ['./login-password-form.component.scss']
+})
+export class LoginPasswordFormComponent extends UserPasswordFormComponent {
+ constructor(
+ public actionLabels: ActionLabelsI18n,
+ public notificationService: NotificationService,
+ public userService: UserService,
+ public authStorageService: AuthStorageService,
+ public formBuilder: CdFormBuilder,
+ public router: Router,
+ public passwordPolicyService: PasswordPolicyService,
+ public authService: AuthService
+ ) {
+ super(
+ actionLabels,
+ notificationService,
+ userService,
+ authStorageService,
+ formBuilder,
+ router,
+ passwordPolicyService
+ );
+ }
+
+ onPasswordChange() {
+ // Logout here because changing the password will change the
+ // session token which will finally lead to a 401 when calling
+ // the REST API the next time. The API HTTP interceptor will
+ // then also redirect to the login page immediately.
+ this.authService.logout();
+ }
+
+ onCancel() {
+ this.authService.logout();
+ }
+}