diff --git a/ui/package-lock.json b/ui/package-lock.json
index 1915086c7..ed12ef05f 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -48,6 +48,7 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
+ "ng-extract-i18n-merge": "^2.9.1",
"prettier": "^3.0.3",
"typescript": "~4.9.5"
}
@@ -11066,6 +11067,88 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "node_modules/ng-extract-i18n-merge": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-2.9.1.tgz",
+ "integrity": "sha512-EJAgJrV2ZSRoH1njMI9lLLtLJkwabkk41ZZyV+U+6h8e5vDCM4zPGjm0NNZFy+YP+/ST+nlvi2CxprDXnjS8BQ==",
+ "dev": true,
+ "dependencies": {
+ "@angular-devkit/architect": "^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0",
+ "@angular-devkit/core": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
+ "@angular-devkit/schematics": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
+ "@schematics/angular": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
+ "xmldoc": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@angular-devkit/build-angular": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
+ }
+ },
+ "node_modules/ng-extract-i18n-merge/node_modules/@angular-devkit/architect": {
+ "version": "0.1700.9",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.9.tgz",
+ "integrity": "sha512-B8OeUrvJj5JsfOJIibpoVjvuZzthPFxf1LvuUXTyQcqDUscJAe/RJBc2woT6ss13Iv/HWt8mgaMPP4CccckdNg==",
+ "dev": true,
+ "dependencies": {
+ "@angular-devkit/core": "17.0.9",
+ "rxjs": "7.8.1"
+ },
+ "engines": {
+ "node": "^18.13.0 || >=20.9.0",
+ "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+ "yarn": ">= 1.13.0"
+ }
+ },
+ "node_modules/ng-extract-i18n-merge/node_modules/@angular-devkit/core": {
+ "version": "17.0.9",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.9.tgz",
+ "integrity": "sha512-r5jqwpWOgowqe9KSDqJ3iSbmsEt2XPjSvRG4DSI2T9s31bReoMtreo8b7wkRa2B3hbcDnstFbn8q27VvJDqRaQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "8.12.0",
+ "ajv-formats": "2.1.1",
+ "jsonc-parser": "3.2.0",
+ "picomatch": "3.0.1",
+ "rxjs": "7.8.1",
+ "source-map": "0.7.4"
+ },
+ "engines": {
+ "node": "^18.13.0 || >=20.9.0",
+ "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+ "yarn": ">= 1.13.0"
+ },
+ "peerDependencies": {
+ "chokidar": "^3.5.2"
+ },
+ "peerDependenciesMeta": {
+ "chokidar": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ng-extract-i18n-merge/node_modules/picomatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
+ "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/ng-extract-i18n-merge/node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
"node_modules/ngx-cookie-service": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-16.1.0.tgz",
@@ -13147,8 +13230,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==",
- "dev": true,
- "optional": true
+ "dev": true
},
"node_modules/saxes": {
"version": "5.0.1",
@@ -15165,6 +15247,15 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
+ "node_modules/xmldoc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz",
+ "integrity": "sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==",
+ "dev": true,
+ "dependencies": {
+ "sax": "^1.2.4"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -23428,6 +23519,60 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "ng-extract-i18n-merge": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-2.9.1.tgz",
+ "integrity": "sha512-EJAgJrV2ZSRoH1njMI9lLLtLJkwabkk41ZZyV+U+6h8e5vDCM4zPGjm0NNZFy+YP+/ST+nlvi2CxprDXnjS8BQ==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/architect": "^0.1301.0 || ^0.1401.0 || ^0.1501.0 || ^0.1601.0 || ^0.1700.0",
+ "@angular-devkit/core": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
+ "@angular-devkit/schematics": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
+ "@schematics/angular": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
+ "xmldoc": "^1.1.2"
+ },
+ "dependencies": {
+ "@angular-devkit/architect": {
+ "version": "0.1700.9",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1700.9.tgz",
+ "integrity": "sha512-B8OeUrvJj5JsfOJIibpoVjvuZzthPFxf1LvuUXTyQcqDUscJAe/RJBc2woT6ss13Iv/HWt8mgaMPP4CccckdNg==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/core": "17.0.9",
+ "rxjs": "7.8.1"
+ }
+ },
+ "@angular-devkit/core": {
+ "version": "17.0.9",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.0.9.tgz",
+ "integrity": "sha512-r5jqwpWOgowqe9KSDqJ3iSbmsEt2XPjSvRG4DSI2T9s31bReoMtreo8b7wkRa2B3hbcDnstFbn8q27VvJDqRaQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "8.12.0",
+ "ajv-formats": "2.1.1",
+ "jsonc-parser": "3.2.0",
+ "picomatch": "3.0.1",
+ "rxjs": "7.8.1",
+ "source-map": "0.7.4"
+ }
+ },
+ "picomatch": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
+ "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
+ "dev": true
+ },
+ "rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ }
+ }
+ },
"ngx-cookie-service": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-16.1.0.tgz",
@@ -24922,8 +25067,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==",
- "dev": true,
- "optional": true
+ "dev": true
},
"saxes": {
"version": "5.0.1",
@@ -26389,6 +26533,15 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
+ "xmldoc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-1.3.0.tgz",
+ "integrity": "sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==",
+ "dev": true,
+ "requires": {
+ "sax": "^1.2.4"
+ }
+ },
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/ui/package.json b/ui/package.json
index 20af641b7..69d9b1c16 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -54,6 +54,7 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
+ "ng-extract-i18n-merge": "^2.9.1",
"prettier": "^3.0.3",
"typescript": "~4.9.5"
}
diff --git a/ui/src/app/account/account.module.ts b/ui/src/app/account/account.module.ts
index 13f05b381..63e934e5e 100644
--- a/ui/src/app/account/account.module.ts
+++ b/ui/src/app/account/account.module.ts
@@ -6,10 +6,11 @@ import { ReactiveFormsModule } from '@angular/forms'
import { routes } from './account.route'
import { PasswordResetInitComponent } from './password/password-reset-init.component'
import { SettingsComponent } from './settings/settings.component'
-import { SharedModule } from '../shared/shared.module'
+import { SharedModule } from '../shared/shared.module';
+import { PasswordComponent } from './password/password.component'
@NgModule({
- declarations: [LoginComponent, PasswordResetInitComponent, SettingsComponent],
+ declarations: [LoginComponent, PasswordResetInitComponent, SettingsComponent, PasswordComponent],
imports: [SharedModule, CommonModule, ReactiveFormsModule, RouterModule.forChild(routes)],
})
export class AccountModule {}
diff --git a/ui/src/app/account/account.route.ts b/ui/src/app/account/account.route.ts
index 8794f2b25..dac2ef290 100644
--- a/ui/src/app/account/account.route.ts
+++ b/ui/src/app/account/account.route.ts
@@ -3,6 +3,7 @@ import { LoginComponent } from './login/login.component'
import { PasswordResetInitComponent } from './password/password-reset-init.component'
import { SettingsComponent } from './settings/settings.component'
import { AuthGuard } from './auth.guard'
+import { PasswordComponent } from './password/password.component'
export const routes: Routes = [
{
@@ -26,4 +27,13 @@ export const routes: Routes = [
},
canActivate: [AuthGuard],
},
+ {
+ path: 'password',
+ component: PasswordComponent,
+ data: {
+ authorities: ['ROLE_USER'],
+ pageTitle: 'global.menu.account.password.string',
+ },
+ canActivate: [AuthGuard],
+ },
]
diff --git a/ui/src/app/account/password/password-reset-init.component.spec.ts b/ui/src/app/account/password/password-reset-init.component.spec.ts
index 3b98a26f2..30bddc76d 100644
--- a/ui/src/app/account/password/password-reset-init.component.spec.ts
+++ b/ui/src/app/account/password/password-reset-init.component.spec.ts
@@ -1,7 +1,7 @@
import { ComponentFixture, TestBed, inject } from '@angular/core/testing'
import { of, throwError } from 'rxjs'
-import { PasswordResetInitService } from '../service/password-reset-init.service'
+import { PasswordService } from '../service/password.service'
import { PasswordResetInitComponent } from './password-reset-init.component'
import { EMAIL_NOT_FOUND_TYPE } from 'src/app/app.constants'
import { HttpClientTestingModule } from '@angular/common/http/testing'
@@ -26,32 +26,29 @@ describe('Component Tests', () => {
expect(comp.errorEmailNotExists).toBeUndefined()
})
- it('notifies of success upon successful requestReset', inject(
- [PasswordResetInitService],
- (service: PasswordResetInitService) => {
- spyOn(service, 'initPasswordReset').and.returnValue(of(new PasswordResetInitResult(true, false, false)))
- comp.resetRequestForm.patchValue({
- email: 'user@domain.com',
- })
+ it('notifies of success upon successful requestReset', inject([PasswordService], (service: PasswordService) => {
+ spyOn(service, 'initPasswordReset').and.returnValue(of(new PasswordResetInitResult(true, false, false)))
+ comp.resetRequestForm.patchValue({
+ email: 'user@domain.com',
+ })
- comp.requestReset()
- const emailControl = comp.resetRequestForm.get('email')!
- emailControl.setValue('valid@email.com')
- fixture.detectChanges()
- expect(comp.success).toEqual('OK')
- expect(comp.error).toBeUndefined()
- expect(comp.errorEmailNotExists).toBeUndefined()
- fixture.whenStable().then(() => {
- expect(true).toBeFalsy()
- const button = fixture.debugElement.query(By.css('#reset'))
- expect(button.nativeElement.disabled).toBeFalsy()
- })
- }
- ))
+ comp.requestReset()
+ const emailControl = comp.resetRequestForm.get('email')!
+ emailControl.setValue('valid@email.com')
+ fixture.detectChanges()
+ expect(comp.success).toEqual('OK')
+ expect(comp.error).toBeUndefined()
+ expect(comp.errorEmailNotExists).toBeUndefined()
+ fixture.whenStable().then(() => {
+ expect(true).toBeFalsy()
+ const button = fixture.debugElement.query(By.css('#reset'))
+ expect(button.nativeElement.disabled).toBeFalsy()
+ })
+ }))
it('notifies of unknown email upon email address not registered/400', inject(
- [PasswordResetInitService],
- (service: PasswordResetInitService) => {
+ [PasswordService],
+ (service: PasswordService) => {
spyOn(service, 'initPasswordReset').and.returnValue(of(new PasswordResetInitResult(false, true, false)))
comp.resetRequestForm.patchValue({
email: 'user@domain.com',
@@ -65,21 +62,18 @@ describe('Component Tests', () => {
}
))
- it('notifies of error upon error response', inject(
- [PasswordResetInitService],
- (service: PasswordResetInitService) => {
- spyOn(service, 'initPasswordReset').and.returnValue(of(new PasswordResetInitResult(false, false, true)))
- comp.resetRequestForm.patchValue({
- email: 'user@domain.com',
- })
- comp.requestReset()
+ it('notifies of error upon error response', inject([PasswordService], (service: PasswordService) => {
+ spyOn(service, 'initPasswordReset').and.returnValue(of(new PasswordResetInitResult(false, false, true)))
+ comp.resetRequestForm.patchValue({
+ email: 'user@domain.com',
+ })
+ comp.requestReset()
- expect(service.initPasswordReset).toHaveBeenCalledWith('user@domain.com')
- expect(comp.success).toBeUndefined()
- expect(comp.errorEmailNotExists).toBeUndefined()
- expect(comp.error).toEqual('ERROR')
- }
- ))
+ expect(service.initPasswordReset).toHaveBeenCalledWith('user@domain.com')
+ expect(comp.success).toBeUndefined()
+ expect(comp.errorEmailNotExists).toBeUndefined()
+ expect(comp.error).toEqual('ERROR')
+ }))
it('should disable the submit button for invalid email address', () => {
const emailControl = comp.resetRequestForm.get('email')!
diff --git a/ui/src/app/account/password/password-reset-init.component.ts b/ui/src/app/account/password/password-reset-init.component.ts
index c0d09e845..92883815d 100644
--- a/ui/src/app/account/password/password-reset-init.component.ts
+++ b/ui/src/app/account/password/password-reset-init.component.ts
@@ -1,7 +1,7 @@
import { Component, AfterViewInit, Renderer2 } from '@angular/core'
import { FormBuilder, FormGroup, Validators } from '@angular/forms'
-import { PasswordResetInitService } from '../service/password-reset-init.service'
+import { PasswordService } from '../service/password.service'
import { EMAIL_NOT_FOUND_TYPE } from 'src/app/app.constants'
import { PasswordResetInitResult } from '../model/password-reset-init-result.model'
@@ -18,7 +18,7 @@ export class PasswordResetInitComponent implements AfterViewInit {
})
constructor(
- private passwordResetInitService: PasswordResetInitService,
+ private passwordResetInitService: PasswordService,
private renderer: Renderer2,
private fb: FormBuilder
) {}
diff --git a/ui/src/app/account/password/password.component.html b/ui/src/app/account/password/password.component.html
new file mode 100644
index 000000000..9e676ded3
--- /dev/null
+++ b/ui/src/app/account/password/password.component.html
@@ -0,0 +1,78 @@
+
+
+
+
{{passwordForUsernameString}}
+
+
+ Password changed!
+
+
+ An error has occurred! The password could not be changed.
+
+
+
+ The password and its confirmation do not match!
+
+
+
+
+
+
diff --git a/ui/src/app/account/password/password.component.scss b/ui/src/app/account/password/password.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/src/app/account/password/password.component.spec.ts b/ui/src/app/account/password/password.component.spec.ts
new file mode 100644
index 000000000..bfe51a81e
--- /dev/null
+++ b/ui/src/app/account/password/password.component.spec.ts
@@ -0,0 +1,107 @@
+import { ComponentFixture, TestBed, async } from '@angular/core/testing'
+import { HttpClientModule, HttpResponse } from '@angular/common/http'
+import { FormBuilder } from '@angular/forms'
+import { Observable, of, throwError } from 'rxjs'
+import { PasswordComponent } from './password.component'
+import { PasswordService } from '../service/password.service'
+import { AccountService } from '../service/account.service'
+
+describe('PasswordComponent', () => {
+ let comp: PasswordComponent
+ let fixture: ComponentFixture
+ let service: PasswordService
+ let accountServiceSpy: jasmine.SpyObj
+
+ beforeEach(() => {
+ accountServiceSpy = jasmine.createSpyObj('AccountService', [
+ 'getAccountData',
+ 'getUserName',
+ 'save',
+ 'getMfaSetup',
+ 'enableMfa',
+ 'disableMfa',
+ ])
+ TestBed.configureTestingModule({
+ imports: [HttpClientModule],
+ declarations: [PasswordComponent],
+ providers: [FormBuilder, { provide: AccountService, useValue: accountServiceSpy }],
+ })
+ .overrideTemplate(PasswordComponent, '')
+ .compileComponents()
+
+ accountServiceSpy = TestBed.inject(AccountService) as jasmine.SpyObj
+ fixture = TestBed.createComponent(PasswordComponent)
+ comp = fixture.componentInstance
+ service = fixture.debugElement.injector.get(PasswordService)
+ })
+
+ it('should show error if passwords do not match', () => {
+ // GIVEN
+ comp.passwordForm.patchValue({
+ newPassword: 'password1',
+ confirmPassword: 'password2',
+ })
+ // WHEN
+ comp.changePassword()
+ // THEN
+ expect(comp.doNotMatch).toBe('ERROR')
+ expect(comp.error).toBeUndefined()
+ expect(comp.success).toBeUndefined()
+ })
+
+ it('should call Auth.changePassword when passwords match', () => {
+ // GIVEN
+ const passwordValues = {
+ currentPassword: 'oldPassword',
+ newPassword: 'myPassword',
+ }
+
+ spyOn(service, 'updatePassword').and.returnValue(of(new HttpResponse({ body: true })))
+
+ comp.passwordForm.patchValue({
+ currentPassword: passwordValues.currentPassword,
+ newPassword: passwordValues.newPassword,
+ confirmPassword: passwordValues.newPassword,
+ })
+
+ // WHEN
+ comp.changePassword()
+
+ // THEN
+ expect(service.updatePassword).toHaveBeenCalledWith(passwordValues.newPassword, passwordValues.currentPassword)
+ })
+
+ it('should set success to OK upon success', function () {
+ // GIVEN
+ spyOn(service, 'updatePassword').and.returnValue(of(new HttpResponse({ body: true })))
+ comp.passwordForm.patchValue({
+ newPassword: 'myPassword',
+ confirmPassword: 'myPassword',
+ })
+
+ // WHEN
+ comp.changePassword()
+
+ // THEN
+ expect(comp.doNotMatch).toBeUndefined()
+ expect(comp.error).toBeUndefined()
+ expect(comp.success).toBe('OK')
+ })
+
+ it('should notify of error if change password fails', function () {
+ // GIVEN
+ spyOn(service, 'updatePassword').and.returnValue(throwError('ERROR'))
+ comp.passwordForm.patchValue({
+ newPassword: 'myPassword',
+ confirmPassword: 'myPassword',
+ })
+
+ // WHEN
+ comp.changePassword()
+
+ // THEN
+ expect(comp.doNotMatch).toBeUndefined()
+ expect(comp.success).toBeUndefined()
+ expect(comp.error).toBe('ERROR')
+ })
+})
diff --git a/ui/src/app/account/password/password.component.ts b/ui/src/app/account/password/password.component.ts
new file mode 100644
index 000000000..909205d8c
--- /dev/null
+++ b/ui/src/app/account/password/password.component.ts
@@ -0,0 +1,59 @@
+import { Component, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup, Validators } from '@angular/forms'
+import { PasswordService } from '../service/password.service'
+import { AccountService } from '../service/account.service'
+
+@Component({
+ selector: 'app-password',
+ templateUrl: './password.component.html',
+ styleUrls: ['./password.component.scss'],
+})
+export class PasswordComponent implements OnInit {
+ doNotMatch: string | undefined
+ error: string | undefined
+ success: string | undefined
+ username: string | undefined | null = null
+ passwordForUsernameString: string | undefined | null = null
+ account: any
+ passwordForm = this.fb.group({
+ currentPassword: ['', [Validators.required]],
+ newPassword: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(50)]],
+ confirmPassword: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(50)]],
+ })
+
+ constructor(
+ private passwordService: PasswordService,
+ private accountService: AccountService,
+ private fb: FormBuilder
+ ) {}
+
+ ngOnInit() {
+ this.accountService.getAccountData().subscribe((account) => {
+ this.account = account
+ this.username = this.accountService.getUsername()
+ this.passwordForUsernameString = $localize`:@@password.title.string:Password for ${this.username} (You)`
+ console.log('username:', this.username)
+ })
+ }
+
+ changePassword() {
+ const newPassword = this.passwordForm.get(['newPassword'])?.value
+ if (newPassword !== this.passwordForm.get(['confirmPassword'])?.value) {
+ this.error = undefined
+ this.success = undefined
+ this.doNotMatch = 'ERROR'
+ } else {
+ this.doNotMatch = undefined
+ this.passwordService.updatePassword(newPassword, this.passwordForm.get(['currentPassword'])?.value).subscribe(
+ () => {
+ this.error = undefined
+ this.success = 'OK'
+ },
+ () => {
+ this.success = undefined
+ this.error = 'ERROR'
+ }
+ )
+ }
+ }
+}
diff --git a/ui/src/app/account/service/account.service.ts b/ui/src/app/account/service/account.service.ts
index 6972b50a3..dc3742157 100644
--- a/ui/src/app/account/service/account.service.ts
+++ b/ui/src/app/account/service/account.service.ts
@@ -167,7 +167,7 @@ export class AccountService {
return this.isIdentityResolved() ? this.accountData.value!.imageUrl : null
}
- getUserName(): string | null {
+ getUsername(): string | null {
let userName: string | null = null
if (this.isIdentityResolved()) {
diff --git a/ui/src/app/account/service/password-reset-init.service.ts b/ui/src/app/account/service/password.service.ts
similarity index 81%
rename from ui/src/app/account/service/password-reset-init.service.ts
rename to ui/src/app/account/service/password.service.ts
index f0e4acc1d..6c7b8fbb6 100644
--- a/ui/src/app/account/service/password-reset-init.service.ts
+++ b/ui/src/app/account/service/password.service.ts
@@ -5,7 +5,7 @@ import { PasswordResetInitResult } from '../model/password-reset-init-result.mod
import { EMAIL_NOT_FOUND_TYPE } from 'src/app/app.constants'
@Injectable({ providedIn: 'root' })
-export class PasswordResetInitService {
+export class PasswordService {
constructor(private http: HttpClient) {}
initPasswordReset(mail: string): Observable {
@@ -17,6 +17,13 @@ export class PasswordResetInitService {
)
}
+ updatePassword(newPassword: string, currentPassword: string): Observable {
+ return this.http.post('/services/userservice/api/account/change-password', {
+ currentPassword,
+ newPassword,
+ })
+ }
+
getResult(res: HttpResponse): PasswordResetInitResult {
if (res.status == 200) {
return new PasswordResetInitResult(true, false, false)
diff --git a/ui/src/app/account/settings/settings.component.spec.ts b/ui/src/app/account/settings/settings.component.spec.ts
index e7944c6e5..57aa2568c 100644
--- a/ui/src/app/account/settings/settings.component.spec.ts
+++ b/ui/src/app/account/settings/settings.component.spec.ts
@@ -97,7 +97,7 @@ describe('SettingsComponent', () => {
})
)
accountServiceSpy.getMfaSetup.and.returnValue(of({ secret: 'test', otp: 'test', qrCode: 'test' }))
- accountServiceSpy.getUserName.and.returnValue('test')
+ accountServiceSpy.getUsername.and.returnValue('test')
fixture.detectChanges()
expect(component.showMfaSetup).toBeFalsy()
diff --git a/ui/src/app/account/settings/settings.component.ts b/ui/src/app/account/settings/settings.component.ts
index 5ff6bb2c9..0680311be 100644
--- a/ui/src/app/account/settings/settings.component.ts
+++ b/ui/src/app/account/settings/settings.component.ts
@@ -54,7 +54,7 @@ export class SettingsComponent implements OnInit {
this.account = account
this.updateForm(account)
this.updateMfaForm(account)
- this.userName = this.accountService.getUserName()
+ this.userName = this.accountService.getUsername()
if (account && !account.mfaEnabled) {
this.accountService.getMfaSetup().subscribe((res) => {
this.mfaSetup = res
diff --git a/ui/src/app/layout/navbar/navbar.component.html b/ui/src/app/layout/navbar/navbar.component.html
index b59da53f7..8ec559460 100644
--- a/ui/src/app/layout/navbar/navbar.component.html
+++ b/ui/src/app/layout/navbar/navbar.component.html
@@ -256,7 +256,7 @@
-
+
Password
diff --git a/ui/src/app/layout/navbar/navbar.component.ts b/ui/src/app/layout/navbar/navbar.component.ts
index 053b0ed3a..5a1fb89b8 100644
--- a/ui/src/app/layout/navbar/navbar.component.ts
+++ b/ui/src/app/layout/navbar/navbar.component.ts
@@ -101,7 +101,7 @@ export class NavbarComponent implements OnInit {
getUserName() {
// return this.isAuthenticated() ? this.userName : null;
- return this.isAuthenticated() ? this.accountService.getUserName() : null
+ return this.isAuthenticated() ? this.accountService.getUsername() : null
}
logout() {
diff --git a/ui/src/i18n/messages copy.xlf b/ui/src/i18n/messages copy.xlf
deleted file mode 100644
index 595912b24..000000000
--- a/ui/src/i18n/messages copy.xlf
+++ /dev/null
@@ -1,421 +0,0 @@
-
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
- Currently selected slide number read by screen reader
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- node_modules/src/ngb-config.ts
- 13
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 4
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 8
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 21
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 43,45
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 51,53
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 59,61
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 66
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 89,91
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 97,99
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 105,107
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 111
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 125
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 137,139
-
-
- src/app/account/settings/settings.component.html
- 260,262
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 151
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 154,157
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 159
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 170
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 174,181
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 204
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 210
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 212
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 215,218
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 229,231
-
-
-
-
-
- src/app/account/settings/settings.component.html
- 244,246
-
-
-
-
-
diff --git a/ui/src/i18n/messages.cs.xlf b/ui/src/i18n/messages.cs.xlf
index 69c82d65a..890172bcd 100644
--- a/ui/src/i18n/messages.cs.xlf
+++ b/ui/src/i18n/messages.cs.xlf
@@ -59,10 +59,10 @@
-
+
node_modules/src/ngb-config.ts
@@ -70,8 +70,8 @@
-
- Slide of
+
+ Slide of
Currently selected slide number read by screen reader
node_modules/src/ngb-config.ts
@@ -264,10 +264,10 @@
-
+
node_modules/src/ngb-config.ts
@@ -276,10 +276,10 @@
-
+
node_modules/src/ngb-config.ts
@@ -287,8 +287,8 @@
-
- Přihlášení se nezdařilo! Zkontrolujte prosím své přihlašovací údaje a zkuste to znovu.
+
+ Přihlášení se nezdařilo! Zkontrolujte prosím své přihlašovací údaje a zkuste to znovu.
src/app/account/login/login.component.html
6
@@ -307,15 +307,15 @@
src/app/account/settings/settings.component.html
- 107
+ 104
-
+
Váš e-mail
src/app/account/login/login.component.html
- 17
+ 16
src/app/account/password/password-reset-init.component.html
@@ -323,7 +323,7 @@
src/app/account/settings/settings.component.html
- 114
+ 110
@@ -331,15 +331,15 @@
Heslo
src/app/account/login/login.component.html
- 23
+ 22
-
+
Vaše heslo
src/app/account/login/login.component.html
- 30
+ 28
@@ -347,7 +347,7 @@
Zadejte prosím MFA kód z aplikace pro ověřování
src/app/account/login/login.component.html
- 36
+ 34
@@ -355,7 +355,7 @@
Neplatný MFA kód
src/app/account/login/login.component.html
- 39
+ 37
@@ -363,7 +363,7 @@
MFA kód
src/app/account/login/login.component.html
- 42
+ 40
@@ -371,7 +371,7 @@
Přihlásit se
src/app/account/login/login.component.html
- 45
+ 43
@@ -379,7 +379,7 @@
Zapomněli jste heslo?
src/app/account/login/login.component.html
- 57
+ 55
@@ -391,8 +391,8 @@
-
- E-mailová adresa není zaregistrovaná! Zkontrolujte ji prosím a zkuste to znovu
+
+ E-mailová adresa není zaregistrovaná! Zkontrolujte ji prosím a zkuste to znovu
src/app/account/password/password-reset-init.component.html
7
@@ -463,8 +463,8 @@
-
- Nastavení uloženo!
+
+ Nastavení uloženo!
src/app/account/settings/settings.component.html
8
@@ -483,7 +483,7 @@
Vaše křestní jméno
src/app/account/settings/settings.component.html
- 29
+ 28
@@ -491,7 +491,7 @@
Vaše křestní jméno je povinné.
src/app/account/settings/settings.component.html
- 43
+ 42
@@ -499,7 +499,7 @@
Vaše jméno musí obsahovat alespoň jeden znak
src/app/account/settings/settings.component.html
- 50
+ 49
@@ -507,7 +507,7 @@
Křestní jméno nesmí být delší než 50 znaků
src/app/account/settings/settings.component.html
- 57
+ 56
@@ -515,7 +515,7 @@
Příjmení
src/app/account/settings/settings.component.html
- 64
+ 63
@@ -523,7 +523,7 @@
Vaše přijmení
src/app/account/settings/settings.component.html
- 73
+ 70
@@ -531,7 +531,7 @@
Vaše příjmení musí být uvedeno.
src/app/account/settings/settings.component.html
- 87
+ 84
@@ -539,7 +539,7 @@
Vaše příjmení musí obsahovat alespoň jeden znak
src/app/account/settings/settings.component.html
- 94
+ 91
@@ -547,7 +547,7 @@
Vaše příjmení nesmí být delší než 50 znaků
src/app/account/settings/settings.component.html
- 101
+ 98
@@ -555,7 +555,7 @@
Jazyk
src/app/account/settings/settings.component.html
- 121
+ 117
@@ -563,11 +563,11 @@
Uložit
src/app/account/settings/settings.component.html
- 131
+ 127
src/app/account/settings/settings.component.html
- 247
+ 241
@@ -575,7 +575,7 @@
Zabezpečení
src/app/account/settings/settings.component.html
- 145
+ 141
@@ -583,7 +583,7 @@
Přidejte další stupeň zabezpečení do svého účtu členského portálu ORCID povolením dvoufázového ověření. Při každém přihlášení budete vyzváni k zadání šestimístného kódu, který zašleme do vámi preferované ověřovací aplikace.
src/app/account/settings/settings.component.html
- 148
+ 144
@@ -591,7 +591,7 @@
Dvoufázové ověření
src/app/account/settings/settings.component.html
- 153
+ 149
@@ -599,23 +599,23 @@
Nastavení dvoufázového ověření aktualizováno
src/app/account/settings/settings.component.html
- 164
+ 160
-
- Nainstalujte si aplikaci pro dvoufázové ověřeníAplikace pro dvoufázové ověření je vyžadována, abyste vytvořili šestimístní kód a pomocí něj získali přístup ke svému účtu pokaždé, když se přihlásíte. Většina aplikací je pro mobilní zařízení. Některé jsou také dostupné jako desktopové a prohlížečové aplikace. Stáhněte a nainstalujte si vámi preferovanou aplikaci pro dvoufázové ověření jako například Google Authenticator, FreeOTP nebo Authy.
+
+ Nainstalujte si aplikaci pro dvoufázové ověřeníAplikace pro dvoufázové ověření je vyžadována, abyste vytvořili šestimístní kód a pomocí něj získali přístup ke svému účtu pokaždé, když se přihlásíte. Většina aplikací je pro mobilní zařízení. Některé jsou také dostupné jako desktopové a prohlížečové aplikace. Stáhněte a nainstalujte si vámi preferovanou aplikaci pro dvoufázové ověření jako například Google Authenticator, FreeOTP nebo Authy.
src/app/account/settings/settings.component.html
- 168
+ 164
-
- Naskenujte tento QR kód vaším zařízenímOtěvřete svou aplikaci pro dvoufázové ověření a naskenujte obrázek níže.
+
+ Naskenujte tento QR kód vaším zařízenímOtěvřete svou aplikaci pro dvoufázové ověření a naskenujte obrázek níže.
src/app/account/settings/settings.component.html
- 176
+ 172
@@ -623,7 +623,7 @@
<strong>Nejde vám QR kód naskenovat?</strong>
src/app/account/settings/settings.component.html
- 198
+ 194
@@ -631,7 +631,7 @@
Získejte namísto toho SMS kód
src/app/account/settings/settings.component.html
- 202
+ 198
@@ -639,15 +639,15 @@
a vložte jej do své aplikace pro dvoufázové ověření místo
src/app/account/settings/settings.component.html
- 204
+ 200
-
- Vložte šestimístní kód z aplikacePo naskenování QR kódu nebo vložení kódu z SMS se v aplikaci pro dvoufázové ověření zobrazí šestimístní kód. Vložte tento kód do pole níže a klikněte na tlačítko Uložit.
+
+ Vložte šestimístní kód z aplikacePo naskenování QR kódu nebo vložení kódu z SMS se v aplikaci pro dvoufázové ověření zobrazí šestimístní kód. Vložte tento kód do pole níže a klikněte na tlačítko Uložit.
src/app/account/settings/settings.component.html
- 207
+ 203
@@ -655,7 +655,7 @@
Nesprávný ověřovací kód
src/app/account/settings/settings.component.html
- 216
+ 212
@@ -663,7 +663,7 @@
Ověřovací kód
src/app/account/settings/settings.component.html
- 225
+ 219
@@ -671,7 +671,7 @@
Poznamenejte si následující záložní kódy, toto je jediný případ, kdy se zobrazí.
src/app/account/settings/settings.component.html
- 232
+ 226
+
+
+ Password for (You)
+ Password label with username param
+
+ src/app/account/password/password.component.ts
+ 16
+
+
+
+
+ Password changed!
+
+ src/app/account/password/password.component.html
+ 7
+
+
+
+
+ An error has occurred! The password could not be changed.
+
+ src/app/account/password/password.component.html
+ 10
+
+
+
+
+ The password and its confirmation do not match!
+
+ src/app/account/password/password.component.html
+ 13
+
+
+
+
+ Current password
+
+ src/app/account/password/password.component.html
+ 20
+
+
+
+
+ Current password
+
+ src/app/account/password/password.component.html
+ 22
+
+
+
+
+ Your password is required.
+
+ src/app/account/password/password.component.html
+ 26
+
+
+ src/app/account/password/password.component.html
+ 39
+
+
+
+
+ New password
+
+ src/app/account/password/password.component.html
+ 32
+
+
+
+
+ New password
+
+ src/app/account/password/password.component.html
+ 34
+
+
+
+
+ Your password is required to be at least 4 characters.
+
+ src/app/account/password/password.component.html
+ 43
+
+
+
+
+ Your password cannot be longer than 50 characters.
+
+ src/app/account/password/password.component.html
+ 47
+
+
+
+
+ New password confirmation
+
+ src/app/account/password/password.component.html
+ 54
+
+
+
+
+ Confirm the new password
+
+ src/app/account/password/password.component.html
+ 56
+
+
+
+
+ Your confirmation password is required.
+
+ src/app/account/password/password.component.html
+ 61
+
+
+
+
+ Your confirmation password is required to be at least 4 characters.
+
+ src/app/account/password/password.component.html
+ 65
+
+
+
+
+ Your confirmation password cannot be longer than 50 characters.
+
+ src/app/account/password/password.component.html
+ 69
+
+
+
+
+ Save
+
+ src/app/account/password/password.component.html
+ 74
+
+
+
+
+ Sorry, an error has occurred
+
+ src/app/shared/error/error-alert.component.html
+ 5
+
+