From 005f70991fb744dbed2c886189d8f335057564f9 Mon Sep 17 00:00:00 2001 From: andrej romanov <50377758+auumgn@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:16:53 +0200 Subject: [PATCH 1/8] allow admins to disable 2fa --- ui/src/app/account/model/account.model.ts | 1 + ui/src/app/account/service/account.service.ts | 4 +- .../account/settings/settings.component.ts | 20 +++++---- ui/src/app/user/user-update.component.html | 44 ++++++++++++++++++- ui/src/app/user/user-update.component.scss | 21 +++++++++ ui/src/app/user/user-update.component.ts | 7 +++ 6 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 ui/src/app/user/user-update.component.scss diff --git a/ui/src/app/account/model/account.model.ts b/ui/src/app/account/model/account.model.ts index 86497e547..44579a4d7 100644 --- a/ui/src/app/account/model/account.model.ts +++ b/ui/src/app/account/model/account.model.ts @@ -1,4 +1,5 @@ export interface IAccount { + id: string activated: boolean authorities: string[] email: string diff --git a/ui/src/app/account/service/account.service.ts b/ui/src/app/account/service/account.service.ts index 32640b39a..781ad6b79 100644 --- a/ui/src/app/account/service/account.service.ts +++ b/ui/src/app/account/service/account.service.ts @@ -89,8 +89,8 @@ export class AccountService { ) } - disableMfa(): Observable { - return this.http.post('/services/userservice/api/account/mfa/off', null, { observe: 'response' }).pipe( + disableMfa(userId: string): Observable { + return this.http.post(`/services/userservice/api/account/${userId}/mfa/off`, null, { observe: 'response' }).pipe( map((res: HttpResponse) => this.isSuccess(res)), catchError(() => { return of(false) diff --git a/ui/src/app/account/settings/settings.component.ts b/ui/src/app/account/settings/settings.component.ts index 243d8e06b..2f698ffa3 100644 --- a/ui/src/app/account/settings/settings.component.ts +++ b/ui/src/app/account/settings/settings.component.ts @@ -118,15 +118,17 @@ export class SettingsComponent implements OnInit { } }) } else { - this.accountService.disableMfa().subscribe({ - next: () => { - this.showMfaUpdated = true - this.accountService.getMfaSetup().subscribe((res) => { - this.mfaSetup = res - }) - }, - error: (err) => console.log('error disabling mfa'), - }) + if (this.account && this.account.id) { + this.accountService.disableMfa(this.account.id).subscribe({ + next: () => { + this.showMfaUpdated = true + this.accountService.getMfaSetup().subscribe((res) => { + this.mfaSetup = res + }) + }, + error: (err) => console.log('error disabling mfa'), + }) + } } } diff --git a/ui/src/app/user/user-update.component.html b/ui/src/app/user/user-update.component.html index cef5a9719..9cf7640ba 100644 --- a/ui/src/app/user/user-update.component.html +++ b/ui/src/app/user/user-update.component.html @@ -68,7 +68,7 @@

+
+
+ +
+

+ Two-factor authentication (ON) + (OFF) +

+
+ +

+ Disable two-factor authentication (2FA) for this user if they are having difficulty signing in to the + member portal. +

+
+ + +
+
+ +
+ Warning sign +
+ Users can enable 2FA from their account settings page +
+
+
+
+
+
+ + +
+
diff --git a/ui/src/app/user/user-update.component.spec.ts b/ui/src/app/user/user-update.component.spec.ts index 103c5f91e..2cfb0aff9 100644 --- a/ui/src/app/user/user-update.component.spec.ts +++ b/ui/src/app/user/user-update.component.spec.ts @@ -128,7 +128,7 @@ describe('UserUpdateComponent', () => { expect(twoFactorAuthenticationCheckbox).toBeTruthy() const checkbox = fixture.debugElement.nativeElement.querySelector('#field_twoFactorAuthentication') checkbox.click() - component.save() + component.saveMfa() expect(accountService.disableMfa).toHaveBeenCalledWith('id') }) @@ -146,7 +146,7 @@ describe('UserUpdateComponent', () => { }) accountService.hasAnyAuthority.and.returnValue(true) fixture.detectChanges() - component.save() + component.saveMfa() const twoFactorAuthenticationCheckbox = fixture.debugElement.query(By.css('#field_twoFactorAuthentication')) expect(twoFactorAuthenticationCheckbox).toBeTruthy() expect(accountService.disableMfa).toHaveBeenCalledTimes(0) @@ -166,7 +166,7 @@ describe('UserUpdateComponent', () => { }) accountService.hasAnyAuthority.and.returnValue(true) fixture.detectChanges() - component.save() + component.saveMfa() const twoFactorAuthenticationCheckbox = fixture.debugElement.query(By.css('#field_twoFactorAuthentication')) expect(twoFactorAuthenticationCheckbox).toBeFalsy() expect(accountService.disableMfa).toHaveBeenCalledTimes(0) diff --git a/ui/src/app/user/user-update.component.ts b/ui/src/app/user/user-update.component.ts index 7bd679d79..1913cd54f 100644 --- a/ui/src/app/user/user-update.component.ts +++ b/ui/src/app/user/user-update.component.ts @@ -48,13 +48,17 @@ export class UserUpdateComponent { salesforceId: new FormControl(null, Validators.required), activated: new FormControl(null), isAdmin: new FormControl(null), - twoFactorAuthentication: new FormControl(null), createdBy: new FormControl(null), createdDate: new FormControl(null), lastModifiedBy: new FormControl(null), lastModifiedDate: new FormControl(null), }) + mfaForm = this.fb.group({ + id: new FormControl(null), + twoFactorAuthentication: new FormControl(null), + }) + memberList = [] as IMember[] hasOwner = false @@ -89,6 +93,7 @@ export class UserUpdateComponent { this.editForm.enable() if (this.existentUser) { this.updateForm(this.existentUser) + this.updateMfaForm(this.existentUser) } }) }) @@ -115,6 +120,12 @@ export class UserUpdateComponent { this.editForm.get('salesforceId')?.valueChanges.subscribe((val) => (this.isSaving = false)) this.editForm.get('mainContact')?.valueChanges.subscribe((val) => (this.isSaving = false)) this.editForm.get('assertionServiceEnabled')?.valueChanges.subscribe((val) => (this.isSaving = false)) + + // MFA + this.mfaForm.get('twoFactorAuthentication')?.valueChanges.subscribe((val) => { + this.isSaving = false + if (val != null) this.disableMfa = !val + }) } updateForm(user: IUser) { @@ -127,7 +138,6 @@ export class UserUpdateComponent { salesforceId: user.salesforceId, activated: user.activated, isAdmin: user.isAdmin, - twoFactorAuthentication: user.mfaEnabled, createdBy: user.createdBy, createdDate: user.createdDate != null ? user.createdDate.format(DATE_TIME_FORMAT) : null, lastModifiedBy: user.lastModifiedBy, @@ -147,6 +157,13 @@ export class UserUpdateComponent { } } + updateMfaForm(user: IUser) { + this.mfaForm.patchValue({ + id: user.id, + twoFactorAuthentication: user.mfaEnabled, + }) + } + getMemberList(): Observable { if (this.hasRoleAdmin()) { return this.memberService.getAllMembers().pipe( @@ -234,8 +251,22 @@ export class UserUpdateComponent { } } - toggleMfa() { - this.disableMfa = !this.editForm.get('twoFactorAuthentication')?.value + saveMfa() { + if (this.mfaForm.valid) { + this.isSaving = true + const userFromForm = this.createFromForm() + this.userService.validate(userFromForm).subscribe((response) => { + const data = response + if (data.valid) { + if (userFromForm != null && this.hasRoleAdmin() && this.disableMfa && userFromForm.id) { + this.subscribeToUpdateResponse(this.accountService.disableMfa(userFromForm.id)) + } + } else { + this.isSaving = false + this.validation = data + } + }) + } } save() { @@ -246,9 +277,6 @@ export class UserUpdateComponent { const data = response if (data.valid) { if (userFromForm.id !== null) { - if (this.hasRoleAdmin() && this.disableMfa && userFromForm.id) { - this.accountService.disableMfa(userFromForm.id).subscribe() - } if (this.currentAccount.id === userFromForm.id) { // ownership change functions redirect to homepage instead of redirecting to users list // as users who lose org owner status shouldn't have access to the users list @@ -308,7 +336,6 @@ export class UserUpdateComponent { mainContact: this.editForm.get(['mainContact'])?.value || false, isAdmin: this.editForm.get(['isAdmin'])?.value || false, salesforceId: this.editForm.get(['salesforceId'])?.value || null, - mfaEnabled: this.editForm.get(['twoFactorAuthentication'])?.value || false, createdBy: this.editForm.get(['createdBy'])?.value || null, createdDate: this.editForm.get(['createdDate'])?.value != null @@ -328,7 +355,7 @@ export class UserUpdateComponent { }) } - protected subscribeToUpdateResponse(result: Observable) { + protected subscribeToUpdateResponse(result: Observable) { result.subscribe({ next: () => this.onUpdateSuccess(), })