Skip to content

Commit 2041154

Browse files
authored
PD-5147 add auth challenge to password reset (#2809)
* PD-5147 add auth challenge to password reset * PD-5147 update test --------- Co-authored-by: andrej romanov <50377758+auumgn@users.noreply.github.com>
1 parent 467090c commit 2041154

17 files changed

Lines changed: 309 additions & 185 deletions

File tree

projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html

Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,44 @@
11
<form
22
[formGroup]="parentForm"
33
(ngSubmit)="onSubmit()"
4-
class="container"
4+
[ngClass]="{ container: data }"
55
[tabindex]="-1"
66
>
77
@if (loading) {
8-
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
8+
<mat-progress-bar
9+
mode="indeterminate"
10+
[ngClass]="{ progress: !data }"
11+
></mat-progress-bar>
912
}
1013
<div
1114
class="icon self-center w-16 h-16 mb-8 content-center ml-auto mr-auto rounded-[50%] text-center"
1215
>
1316
<mat-icon class="material-symbols-outlined">person_check</mat-icon>
1417
</div>
15-
<h1
16-
class="orc-font-heading-small text-center font-normal"
17-
cdkFocusInitial
18-
i18n
19-
>
18+
<h1 class="orc-font-heading-small text-center font-normal" i18n>
2019
Verify your ORCID account
2120
</h1>
2221
<p class="orc-font-body-small text-center mb-8">
23-
<span i18n>Enter</span>&nbsp;@if (data.showPasswordField) {<ng-container
24-
i18n
22+
<span i18n>Enter</span>&nbsp;@if (showPasswordField) {<ng-container i18n
2523
>your ORCID account password</ng-container
26-
>&nbsp;}@if (data.showPasswordField && data.showTwoFactorField)
27-
{<ng-container i18n>and</ng-container>&nbsp;}@if (data.showTwoFactorField)
28-
{<ng-container i18n>a two-factor authentication code</ng-container
29-
>&nbsp;}to&nbsp;{{ data.actionDescription }}@if (data.memberName) {&nbsp;<b
30-
>{{ data.memberName }}</b
31-
>}
24+
>&nbsp;}@if (showPasswordField && showTwoFactorField) {<ng-container i18n
25+
>and</ng-container
26+
>&nbsp;}@if (showTwoFactorField) {<ng-container i18n
27+
>a two-factor authentication code</ng-container
28+
>&nbsp;}to&nbsp;{{ actionDescription }}@if (boldText) {&nbsp;<b>{{
29+
boldText
30+
}}</b
31+
>}@if (trailingText) {&nbsp;{{ trailingText }}}
3232
</p>
3333

34-
@if (data.showPasswordField) {
34+
@if (showPasswordField) {
3535
<div class="row mb-6 flex flex-wrap">
3636
<mat-label
3737
class="orc-font-small-print block mb-2!"
3838
id="password-input-label"
3939
[ngClass]="{
4040
error:
41-
parentForm.get(data.passwordControlName)?.invalid &&
42-
passwordWasTouched
41+
parentForm.get(passwordControlName)?.invalid && passwordWasTouched
4342
}"
4443
>
4544
<strong i18n="@@register.password">Password</strong>
@@ -51,43 +50,41 @@
5150
<input
5251
matInput
5352
#passwordInput
54-
[formControlName]="data.passwordControlName"
53+
[formControlName]="passwordControlName"
5554
id="password"
5655
type="password"
5756
aria-labelledby="password-input-label"
5857
[errorStateMatcher]="errorMatcher"
5958
[ngClass]="{
6059
error:
61-
parentForm.get(data.passwordControlName)?.invalid &&
62-
passwordWasTouched
60+
parentForm.get(passwordControlName)?.invalid && passwordWasTouched
6361
}"
6462
/>
6563
</mat-form-field>
6664

67-
@if (parentForm.get(data.passwordControlName)?.hasError('required') &&
65+
@if (parentForm.get(passwordControlName)?.hasError('required') &&
6866
passwordWasTouched) {
6967
<mat-error
7068
class="w-full orc-font-small-print mt-2"
7169
i18n="@@account.passwordCannotBeEmpty"
7270
>
7371
Password cannot be empty
7472
</mat-error>
75-
} @if (parentForm.get(data.passwordControlName)?.hasError('invalid')) {
73+
} @if (parentForm.get(passwordControlName)?.hasError('invalid')) {
7674
<mat-error class="w-full orc-font-small-print mt-2" i18n>
7775
The password does not match our records
7876
</mat-error>
7977
}
8078
</div>
81-
} @if (data.showTwoFactorField) { @if (!showRecoveryCode) {
79+
} @if (showTwoFactorField) { @if (!showRecoveryCode) {
8280
<div>
8381
<mat-label
84-
class="orc-font-small-print font-bold! block mb-2!"
82+
class="orc-font-small-print font-bold! block mb-2! text-left"
8583
id="2fa-input-label"
8684
i18n="@@account.twoPassword"
8785
[ngClass]="{
8886
error:
89-
twoFactorCodeWasTouched &&
90-
parentForm.get(data.codeControlName)?.invalid
87+
twoFactorCodeWasTouched && parentForm.get(codeControlName)?.invalid
9188
}"
9289
>
9390
Two-factor authentication
@@ -102,40 +99,39 @@
10299
type="text"
103100
inputmode="numeric"
104101
aria-labelledby="2fa-input-label"
105-
[formControlName]="data.codeControlName"
102+
[formControlName]="codeControlName"
106103
matInput
107104
[errorStateMatcher]="errorMatcher"
108105
#twoFactorCodeInput
109106
[ngClass]="{
110107
error:
111-
twoFactorCodeWasTouched &&
112-
parentForm.get(data.codeControlName)?.invalid
108+
twoFactorCodeWasTouched && parentForm.get(codeControlName)?.invalid
113109
}"
114110
/>
115111
</mat-form-field>
116112
<div class="flex orc-font-small-print">
117-
<div class="mt-2 grow">
118-
@if (!parentForm.get(data.codeControlName)?.invalid ||
113+
<div class="mt-2 grow text-left">
114+
@if (!parentForm.get(codeControlName)?.invalid ||
119115
!twoFactorCodeWasTouched) {
120116
<mat-hint class="leading-6" i18n="@@account.enterTheCode">
121117
Enter the code from your two-factor authentication app
122118
</mat-hint>
123-
} @if (parentForm.get(data.codeControlName)?.hasError('invalid')) {
119+
} @if (parentForm.get(codeControlName)?.hasError('invalid')) {
124120
<mat-error>
125121
<ng-container i18n="@@ngOrcid.signin.2fa.badVerificationCode">
126122
Invalid authentication code
127123
</ng-container>
128124
</mat-error>
129125
} @if ( twoFactorCodeWasTouched &&
130-
parentForm.get(data.codeControlName)?.hasError('required')) {
126+
parentForm.get(codeControlName)?.hasError('required')) {
131127
<mat-error>
132128
<ng-container i18n="@@ngOrcid.signin.2fa.verificationCodeRequired">
133129
Authentication code is required
134130
</ng-container>
135131
</mat-error>
136132
} @if ( twoFactorCodeWasTouched &&
137-
(parentForm.get(data.codeControlName)?.hasError('minlength') ||
138-
parentForm.get(data.codeControlName)?.hasError('maxlength'))) {
133+
(parentForm.get(codeControlName)?.hasError('minlength') ||
134+
parentForm.get(codeControlName)?.hasError('maxlength'))) {
139135
<mat-error>
140136
<ng-container i18n="@@ngOrcid.signin.2fa.badVerificationCodeLength">
141137
Invalid authentication code length
@@ -146,25 +142,24 @@
146142
<mat-hint
147143
[ngClass]="{
148144
error:
149-
twoFactorCodeWasTouched &&
150-
parentForm.get(data.codeControlName)?.invalid
145+
twoFactorCodeWasTouched && parentForm.get(codeControlName)?.invalid
151146
}"
152147
class="mt-2 self-center leading-6"
153148
>
154-
{{ parentForm.get(data.codeControlName)?.value?.length || 0 }}/6
149+
{{ parentForm.get(codeControlName)?.value?.length || 0 }}/6
155150
</mat-hint>
156151
</div>
157152
</div>
158153
} @else {
159154
<div>
160155
<mat-label
161-
class="orc-font-small-print font-bold! block mb-2!"
156+
class="orc-font-small-print font-bold! block mb-2! text-left"
162157
id="recovery-input-label"
163158
i18n="@@ngOrcid.signin.2fa.recoveryCode"
164159
[ngClass]="{
165160
error:
166161
twoFactorRecoveryCodeWasTouched &&
167-
parentForm.get(data.recoveryControlName)?.invalid
162+
parentForm.get(recoveryControlName)?.invalid
168163
}"
169164
>
170165
Recovery code
@@ -179,40 +174,40 @@
179174
type="text"
180175
inputmode="numeric"
181176
aria-labelledby="recovery-input-label"
182-
[formControlName]="data.recoveryControlName"
177+
[formControlName]="recoveryControlName"
183178
#twoFactorRecoveryCodeInput
184179
[errorStateMatcher]="errorMatcher"
185180
matInput
186181
[ngClass]="{
187182
error:
188183
twoFactorRecoveryCodeWasTouched &&
189-
parentForm.get(data.recoveryControlName)?.invalid
184+
parentForm.get(recoveryControlName)?.invalid
190185
}"
191186
/>
192187
</mat-form-field>
193188
<div class="flex orc-font-small-print">
194-
<div class="mt-2 grow">
195-
@if (!parentForm.get(data.recoveryControlName)?.invalid ||
189+
<div class="mt-2 grow text-left">
190+
@if (!parentForm.get(recoveryControlName)?.invalid ||
196191
!twoFactorRecoveryCodeWasTouched) {
197192
<mat-hint class="leading-6" i18n="@@ngOrcid.signin.2fa.noDevice2"
198193
>Enter a recovery code</mat-hint
199194
>
200-
} @if (parentForm.get(data.recoveryControlName)?.hasError('invalid')) {
195+
} @if (parentForm.get(recoveryControlName)?.hasError('invalid')) {
201196
<mat-error>
202197
<ng-container i18n="@@ngOrcid.signin.2fa.badRecoveryCode">
203198
Invalid recovery code
204199
</ng-container>
205200
</mat-error>
206201
} @if (twoFactorRecoveryCodeWasTouched &&
207-
parentForm.get(data.recoveryControlName)?.hasError('required')) {
202+
parentForm.get(recoveryControlName)?.hasError('required')) {
208203
<mat-error>
209204
<ng-container i18n="@@ngOrcid.signin.2fa.recoveryCodeRequired">
210205
Recovery code is required
211206
</ng-container>
212207
</mat-error>
213208
} @if (twoFactorRecoveryCodeWasTouched &&
214-
(parentForm.get(data.recoveryControlName)?.hasError('minlength') ||
215-
parentForm.get(data.recoveryControlName)?.hasError('maxlength'))) {
209+
(parentForm.get(recoveryControlName)?.hasError('minlength') ||
210+
parentForm.get(recoveryControlName)?.hasError('maxlength'))) {
216211
<mat-error>
217212
<ng-container i18n="@@ngOrcid.signin.2fa.badRecoveryCodeLength">
218213
Invalid recovery code length
@@ -224,11 +219,11 @@
224219
[ngClass]="{
225220
error:
226221
twoFactorRecoveryCodeWasTouched &&
227-
parentForm.get(data.recoveryControlName)?.invalid
222+
parentForm.get(recoveryControlName)?.invalid
228223
}"
229224
class="mt-2 self-center leading-6"
230225
>
231-
{{ parentForm.get(data.recoveryControlName)?.value?.length || 0 }}/10
226+
{{ parentForm.get(recoveryControlName)?.value?.length || 0 }}/10
232227
</mat-hint>
233228
</div>
234229
</div>
@@ -262,7 +257,7 @@
262257
Use your authentication app instead
263258
</a>
264259
</div>
265-
} <br />
260+
}
266261
<div class="text-center orc-font-body-small mt-4">
267262
<p i18n="@@ngOrcid.signin.2fa.noDeviceOrRecovery" class="m-0 leading-6">
268263
Don't have your device or recovery code?
@@ -296,7 +291,7 @@
296291
<button
297292
mat-button
298293
type="button"
299-
mat-dialog-close
294+
(click)="onCancel()"
300295
class="text-center orc-font-body-small h-10! w-full block content-center cancel font-normal!"
301296
i18n
302297
>

projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ mat-progress-bar {
7979
max-width: 580px;
8080
}
8181

82+
.progress {
83+
margin-top: -48px !important;
84+
margin-left: -48px !important;
85+
}
86+
8287
@media (max-width: 600px) {
8388
.container {
8489
padding: 32px;

projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.spec.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,17 @@ describe('AuthChallengeComponent', () => {
1818
let mockDialogRef: any
1919

2020
beforeEach(async () => {
21-
// 1. Create a mock form to pass into the dialog data
2221
form = new FormGroup({
2322
passwordControl: new FormControl(''),
2423
twoFactorCode: new FormControl(''),
2524
twoFactorRecoveryCode: new FormControl(''),
2625
})
2726

28-
// 2. Mock the MatDialogRef so updateSize() doesn't throw an error
2927
mockDialogRef = {
3028
updateSize: jasmine.createSpy('updateSize'),
3129
close: jasmine.createSpy('close'),
3230
}
3331

34-
// 3. Mock the MAT_DIALOG_DATA
3532
const mockDialogData = {
3633
parentForm: form,
3734
showPasswordField: true,
@@ -40,7 +37,7 @@ describe('AuthChallengeComponent', () => {
4037
codeControlName: 'twoFactorCode',
4138
recoveryControlName: 'twoFactorRecoveryCode',
4239
passwordControlName: 'passwordControl',
43-
actionDescription: 'login', // Mock label
40+
actionDescription: 'login',
4441
}
4542

4643
await TestBed.configureTestingModule({
@@ -68,7 +65,7 @@ describe('AuthChallengeComponent', () => {
6865

6966
codeControl?.setValue('')
7067
expect(codeControl?.hasError('required')).toBeTrue()
71-
expect(codeControl?.touched).toBeFalse() // Checks the markAsUntouched logic
68+
expect(codeControl?.touched).toBeFalse()
7269

7370
recoveryControl?.setValue('')
7471
expect(recoveryControl?.hasError('required')).toBeFalse()
@@ -148,28 +145,19 @@ describe('AuthChallengeComponent', () => {
148145
})
149146
})
150147

151-
describe('Dialog Data (formerly @Input)', () => {
152-
it('should hide help text links if showHelpText is false', () => {
153-
// Modify data directly
154-
component.data.showHelpText = false
155-
fixture.detectChanges()
156-
157-
const links = fixture.debugElement.query(
158-
By.css('[data-testid="help-text-links"]')
159-
)
160-
expect(links).toBeFalsy()
161-
})
162-
148+
describe('Component properties (formerly Dialog Data @Input)', () => {
163149
it('should hide two-factor field if showTwoFactorField is false', () => {
164-
component.data.showTwoFactorField = false
150+
// Set on the component property directly — the template binds to this,
151+
// not to data, since ngOnInit already copied data into component fields
152+
component.showTwoFactorField = false
165153
fixture.detectChanges()
166154

167155
const codeInput = fixture.debugElement.query(By.css('#twoFactorCode'))
168156
expect(codeInput).toBeFalsy()
169157
})
170158

171159
it('should hide password field if showPasswordField is false', () => {
172-
component.data.showPasswordField = false
160+
component.showPasswordField = false
173161
fixture.detectChanges()
174162

175163
const passwordInput = fixture.debugElement.query(By.css('#password'))

0 commit comments

Comments
 (0)