diff --git a/projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html b/projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html index 10f5030c0..f8f4a5e97 100644 --- a/projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html +++ b/projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html @@ -276,6 +276,7 @@

> {{ primaryLabel }} + diff --git a/projects/orcid-ui/src/lib/components/step-view/step-view.component.scss b/projects/orcid-ui/src/lib/components/step-view/step-view.component.scss index ae993fb23..0d83e1b71 100644 --- a/projects/orcid-ui/src/lib/components/step-view/step-view.component.scss +++ b/projects/orcid-ui/src/lib/components/step-view/step-view.component.scss @@ -62,7 +62,9 @@ .orcid-step-view__actions { display: flex; - gap: var(--orcid-space-4, 16px); + flex-direction: column; + align-items: center; + gap: var(--orcid-space-m, 24px); justify-content: center; } @@ -70,8 +72,8 @@ width: 100%; } -.orcid-step-view__actions--full button { - flex: 1 1 auto; +.orcid-step-view__actions--full .orcid-step-view__primary-action { + width: 100%; } .orcid-step-view__primary-action { @@ -83,6 +85,16 @@ opacity: 0.6; } +.orcid-step-view__secondary-action { + background: none; + border: 0; + padding: 0; + cursor: pointer; + text-decoration: underline; + color: var(--orcid-color-brand-secondary-dark, #085c77); + font: inherit; +} + @media (max-width: 767px) { .orcid-step-view { padding: var(--orcid-space-4, 16px); diff --git a/projects/orcid-ui/src/lib/components/step-view/step-view.component.ts b/projects/orcid-ui/src/lib/components/step-view/step-view.component.ts index 5488fce0c..b4050c854 100644 --- a/projects/orcid-ui/src/lib/components/step-view/step-view.component.ts +++ b/projects/orcid-ui/src/lib/components/step-view/step-view.component.ts @@ -31,8 +31,13 @@ export class OrcidStepViewComponent { /** Disables the primary action button when true. */ @Input() primaryDisabled = false + /** Optional text for a secondary action, shown as a link below the primary button. */ + @Input() secondaryLabel = '' + /** Whether footer buttons should fill the container width. */ @Input() fullWidthActions = true @Output() primaryAction = new EventEmitter() + + @Output() secondaryAction = new EventEmitter() } diff --git a/src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html b/src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html index a085f90de..59ac9207e 100644 --- a/src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html +++ b/src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html @@ -1,5 +1,25 @@ - @if (success) { + @if (recoveryPhoneOutcome === 'added' || recoveryPhoneOutcome === 'updated') { + +
+ @if (recoveryPhoneOutcome === 'added') { + Your recovery phone number has been added + } @else { + Your recovery phone number has been updated + } +
+
+ } @if (recoveryPhoneOutcome === 'failed') { + +
+ Your recovery phone number was not updated +
+
+ } @if (success) {
Two-factor authentication has been disabled @@ -62,6 +82,9 @@

Sign in with 2FA

Authentication app

+ @if (recoveryPhoneTogglz && !loadingTogglz) { + Enabled: + } {{ twoFactorInfo?.twoFactorCreationDate | monthDayYearDateToString }} @@ -71,7 +94,11 @@

Sign in with 2FA

+ @if (recoveryPhoneTogglz && !loadingTogglz) { +

2FA backup

+ } @else {

Account recovery

+ }

Account recovery Recover access to your ORCID account if you can't use your authentication app.

+ + @if (recoveryPhoneTogglz && !loadingTogglz) { +
+ +
+
+

+ Recovery phone number + @if (hasRecoveryPhone) { + {{ + twoFactorInfo?.maskedRecoveryPhoneNumber + }} + } +

+ @if (hasRecoveryPhone) { + + @if (twoFactorInfo?.recoveryPhoneModified) { + Modified: + } @else { + Enabled: + } + {{ recoveryPhoneDate | monthDayYearDateToString }} + + } +
+ +
+
+ } +
Account recovery 2FA recovery codes

+ @if (recoveryPhoneTogglz && !loadingTogglz) { + Enabled: + } {{ twoFactorInfo?.recoveryCodeCreationDate | monthDayYearDateToString }} @@ -100,10 +184,27 @@

Account recovery

+ @if (recoveryPhoneTogglz && !loadingTogglz) { +

+ Disable two-factor authentication +

+ }

You can disable two-factor authentication at any time. Turning 2FA off will reset any account recovery options you have set up.

+ @if (recoveryPhoneTogglz && !loadingTogglz) { + + Learn more about two-factor authentication + + + } @else { Account recovery Find out more about disabling two-factor authentication + } + @if (codeSent) { +

+ Verification code sent to + {{ sentToNumber }}. @if (resendCountdown > 0) { + Resend in {{ resendCountdown }} seconds + } +

+ } +
+ +
+ + +
+ @if (codeErrorMessage) { + {{ + codeErrorMessage + }} + } @else { + Enter the 6-digit verification code sent to your device + } + +
+
+ + @if (generalErrorMessage) { + +
{{ generalErrorMessage }}
+
+ } + + + } + + diff --git a/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.scss b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.scss new file mode 100644 index 000000000..a68088d2b --- /dev/null +++ b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.scss @@ -0,0 +1,260 @@ +/* + * The router container is a flex row, so the host has to be told to fill it. + * Left to size itself the page is only as wide as the card and the centring + * below has nothing to centre within. + */ +:host { + display: block; + width: 100%; +} + +.recovery-phone-page { + display: flex; + justify-content: center; + padding: var(--orcid-space-l, 32px) var(--orcid-space-base, 16px); +} + +.recovery-phone-page orcid-step-view { + display: block; + width: 100%; + max-width: 580px; +} + +.recovery-phone-notice { + display: flex; + align-items: flex-start; + gap: var(--orcid-space-base, 16px); + border: 2px solid var(--orcid-color-notice-important, #ff9c00); + border-radius: 4px; + background-color: rgba(255, 236, 171, 0.05); + padding: var(--orcid-space-base, 16px); + margin-bottom: var(--orcid-space-l, 32px); + text-align: left; + + p { + margin: 0; + } + + p + p { + margin-top: var(--orcid-space-base, 16px); + } +} + +.recovery-phone-notice__icon { + color: var(--orcid-color-notice-important, #ff9c00); + flex: 0 0 24px; + width: 24px; + height: 24px; + font-size: 24px; + line-height: 24px; +} + +.recovery-phone-notice__emphasis { + font-style: italic; +} + +.recovery-phone-subheading { + font-weight: bold; + margin: 0 0 var(--orcid-space-base, 16px); + text-align: left; +} + +.recovery-phone-current { + margin: 0 0 var(--orcid-space-base, 16px); + text-align: left; +} + +.recovery-phone-current__value { + font-weight: bold; + margin-left: var(--orcid-space-xs, 4px); +} + +.recovery-phone-help { + margin: 0 0 var(--orcid-space-base, 16px); + text-align: left; +} + +.recovery-phone-form { + display: flex; + flex-direction: column; + gap: var(--orcid-space-m, 24px); + text-align: left; +} + +/* + * The design draws the country selector and the number as one bordered field: + * a grey segment holding the flag and its chevron, then the dial code, then the + * number itself, all inside a single 40px box. The library renders the pieces + * but styles them as a plain input, so the border moves out to the container + * and the inner input gives up its own. + */ +.phone-input-shell { + display: block; + + /* + * The library exposes these, so the flag size and the tint behind it are set + * through its own variables rather than by overriding the elements: it keeps + * the flag sprite sharp and leaves hover and focus to the library. + */ + --iti-flag-width: 28px; + --iti-country-selector-bg: var(--orcid-color-background-lightest, #fafafa); + --iti-border-color: var(--orcid-color-border-subtle, #bdbdbd); + --iti-spacer-horizontal: 8px; + + ::ng-deep .iti { + display: block; + width: 100%; + } + + /* + * Never set padding-left here. The library measures the flag chip and the + * dial code and writes the left padding itself; overriding it puts the + * number on top of the dial code. + */ + ::ng-deep .iti__tel-input, + ::ng-deep input[type='tel'] { + width: 100%; + height: 40px; + box-sizing: border-box; + border: 1px solid var(--orcid-color-border-subtle, #bdbdbd); + border-radius: 2px; + padding-top: 8px; + padding-bottom: 8px; + padding-right: 8px; + font-size: 14px; + line-height: 21px; + letter-spacing: 0.25px; + color: var(--orcid-color-text-dark-high, #000000); + } + + ::ng-deep .iti__tel-input::placeholder { + color: var(--orcid-color-text-placeholder, #707070); + } + + /* + * The flag and its chevron sit on a tinted segment, divided from the number + * by a hairline; the dial code then reads as the start of the number itself. + * The tint goes on the primary block rather than the button, which the + * library paints white on hover and focus. + */ + ::ng-deep .iti__country-container { + padding: 1px; + height: 38px; + } + + ::ng-deep .iti__selected-country { + border-radius: 2px 0 0 2px; + height: 100%; + } + + ::ng-deep .iti__selected-country-primary { + border-right: 1px solid var(--orcid-color-border-subtle, #bdbdbd); + border-radius: 2px 0 0 2px; + height: 100%; + padding: 0 6px 0 8px; + display: flex; + align-items: center; + } + + /* the dial code is set like the rest of the number, not emphasised */ + ::ng-deep .iti__selected-dial-code { + padding-left: 8px; + font-size: 14px; + font-weight: 400; + line-height: 21px; + letter-spacing: 0.25px; + color: var(--orcid-color-text-dark-high, #000000); + } + + /* + * The library writes an inline height on the open list, measured before its + * own stylesheet has capped the list, so the panel comes out as tall as all + * 240-odd countries laid end to end. Letting it size to its content puts it + * back to the search box plus the scrollable list it already renders. + */ + ::ng-deep .iti__country-selector { + height: auto !important; + } +} + +/* a rejected number turns the whole field red, not just the message below it */ +.phone-input-shell--invalid { + ::ng-deep .iti__tel-input, + ::ng-deep input[type='tel'] { + border-color: var(--orcid-color-state-warning-darkest, #b71c1c); + } +} + +/* + * The consent copy and the two links it ends with are one run of text in the + * design, so the links follow on the next line with nothing but the line + * height between them: as separate children of the form they would be pushed + * apart by its 24px gap. + */ +.recovery-phone-consent p { + margin: 0; +} + +.recovery-phone-error { + display: block; + margin-top: var(--orcid-space-s, 8px); +} + +.send-code { + display: flex; + flex-direction: column; + gap: var(--orcid-space-s, 8px); +} + +.send-code__button { + width: 100%; +} + +.send-code__helper { + margin: 0; + text-align: center; +} + +.verification-field { + display: flex; + flex-direction: column; + gap: var(--orcid-space-s, 8px); +} + +.verification-field__label { + font-weight: bold; +} + +.verification-field__input { + height: 40px; + border: 1px solid var(--orcid-color-border-subtle, #bdbdbd); + border-radius: 2px; + padding: 8px; + width: 100%; + box-sizing: border-box; +} + +.verification-field__input:disabled { + background-color: var(--orcid-color-background-light, #eeeeee); +} + +/* + * A rejected code turns the whole field red and the message takes the + * microcopy's place below it rather than stacking under it, which is how the + * code field on the sign-in page behaves. + */ +.verification-field--invalid .verification-field__input { + border-color: var(--orcid-color-state-warning-darkest, #b71c1c); +} + +.verification-field__helper { + display: flex; + justify-content: space-between; + gap: var(--orcid-space-s, 8px); +} + +@media (max-width: 767px) { + .recovery-phone-page { + padding: 0; + } +} diff --git a/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.scss-theme.scss b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.scss-theme.scss new file mode 100644 index 000000000..242265616 --- /dev/null +++ b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.scss-theme.scss @@ -0,0 +1,19 @@ +@use '@angular/material' as mat; +@import 'src/assets/scss/material.orcid-theme.scss'; + +@mixin recovery-phone-theme($theme) { + $primary: map-get($theme, primary); + $config: mat.m2-define-typography-config(); + + .send-code__button { + background-color: var(--orcid-color-brand-secondary-dark, #085c77); + color: #ffffff; + } + + .recovery-phone-error, + .verification-field--invalid .verification-field__label, + .verification-field--invalid .verification-field__helper { + color: var(--orcid-color-state-warning-darkest, #b71c1c); + } +} +@include recovery-phone-theme($orcid-app-theme); diff --git a/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.spec.ts b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.spec.ts new file mode 100644 index 000000000..6dc6325da --- /dev/null +++ b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.spec.ts @@ -0,0 +1,373 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core' +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing' +import { ReactiveFormsModule } from '@angular/forms' +import { MatDialog, MatDialogRef } from '@angular/material/dialog' +import { Router } from '@angular/router' +import { RouterTestingModule } from '@angular/router/testing' +import { EventEmitter } from '@angular/core' +import { Subject, of, throwError } from 'rxjs' +import IntlTelInput from '@intl-tel-input/angular' + +import { RecoveryPhoneComponent } from './recovery-phone.component' +import { ApplicationRoutes } from '../../../constants' +import { TogglzService } from '../../../core/togglz/togglz.service' +import { TwoFactorAuthenticationService } from '../../../core/two-factor-authentication/two-factor-authentication.service' +import { Status } from '../../../types/two-factor.endpoint' + +describe('RecoveryPhoneComponent', () => { + let component: RecoveryPhoneComponent + let fixture: ComponentFixture + let twoFactorService: jasmine.SpyObj + let togglzService: jasmine.SpyObj + let dialog: jasmine.SpyObj + let router: Router + + // Stands in for the auth challenge dialog the page opens on load + let submitAttempt: EventEmitter + let cancelAttempt: EventEmitter + let afterClosed: Subject + let dialogInstance: any + let dialogRef: any + + const status = (overrides: Partial = {}): Status => + ({ enabled: true, ...overrides }) as Status + + function build(flagEnabled = true, statusValue: Status = status()) { + togglzService.getStateOf.and.returnValue(of(flagEnabled)) + twoFactorService.checkState.and.returnValue(of(statusValue)) + fixture = TestBed.createComponent(RecoveryPhoneComponent) + component = fixture.componentInstance + fixture.detectChanges() + } + + /** Drives the challenge dialog to a pass, which is what unlocks the form. */ + function passChallenge() { + twoFactorService.verifyRecoveryPhoneChallenge.and.returnValue( + of({ success: true } as any) + ) + submitAttempt.emit() + afterClosed.next(true) + fixture.detectChanges() + } + + beforeEach(async () => { + submitAttempt = new EventEmitter() + cancelAttempt = new EventEmitter() + afterClosed = new Subject() + dialogInstance = { + submitAttempt, + cancelAttempt, + loading: false, + processBackendResponse: jasmine.createSpy('processBackendResponse'), + } + dialogRef = { + componentInstance: dialogInstance, + afterClosed: () => afterClosed.asObservable(), + close: jasmine.createSpy('close').and.callFake((value?: boolean) => { + afterClosed.next(!!value) + }), + } as unknown as MatDialogRef + + twoFactorService = jasmine.createSpyObj('TwoFactorAuthenticationService', [ + 'checkState', + 'verifyRecoveryPhoneChallenge', + 'sendRecoveryPhoneCode', + 'saveRecoveryPhone', + ]) + togglzService = jasmine.createSpyObj('TogglzService', ['getStateOf']) + dialog = jasmine.createSpyObj('MatDialog', ['open']) + dialog.open.and.returnValue(dialogRef) + + await TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, RouterTestingModule, IntlTelInput], + declarations: [RecoveryPhoneComponent], + providers: [ + { provide: TwoFactorAuthenticationService, useValue: twoFactorService }, + { provide: TogglzService, useValue: togglzService }, + { provide: MatDialog, useValue: dialog }, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }).compileComponents() + + router = TestBed.inject(Router) + spyOn(router, 'navigate') + }) + + it('sends the user back to account settings when the feature is off', () => { + build(false) + + expect(router.navigate).toHaveBeenCalledWith( + [ApplicationRoutes.account], + jasmine.objectContaining({ fragment: '2FA' }) + ) + expect(dialog.open).not.toHaveBeenCalled() + }) + + it('sends the user back when 2FA is not enabled', () => { + build(true, status({ enabled: false })) + + expect(router.navigate).toHaveBeenCalled() + expect(dialog.open).not.toHaveBeenCalled() + }) + + it('asks for the authentication challenge before anything else', () => { + build() + + expect(dialog.open).toHaveBeenCalled() + expect(component.title).toBe('Add a recovery phone number') + }) + + it('treats an existing number as a change rather than an addition', () => { + build(true, status({ maskedRecoveryPhoneNumber: '***********1234' })) + + expect(component.managingExistingNumber).toBeTrue() + expect(component.title).toBe('Manage your recovery phone number') + expect(component.primaryLabel).toBe('Update recovery phone number') + expect(component.maskedRecoveryPhoneNumber).toBe('***********1234') + }) + + it('leaves the page when the challenge is cancelled', () => { + build() + ;(router.navigate as jasmine.Spy).calls.reset() + + afterClosed.next(false) + + expect(router.navigate).toHaveBeenCalled() + }) + + it('reports a failed challenge back into the dialog', () => { + build() + twoFactorService.verifyRecoveryPhoneChallenge.and.returnValue( + of({ success: false, invalidPassword: true } as any) + ) + + submitAttempt.emit() + + expect(dialogInstance.processBackendResponse).toHaveBeenCalled() + expect(dialogRef.close).not.toHaveBeenCalledWith(true) + }) + + it('enables the code field and starts the countdown once a code is sent', () => { + build() + passChallenge() + twoFactorService.sendRecoveryPhoneCode.and.returnValue( + of({ success: true, resendAfterSeconds: 30 }) + ) + component.form.get('phoneNumber')?.setValue('+441234567890') + + component.sendCode() + + expect(component.codeSent).toBeTrue() + expect(component.verificationCodeControl?.enabled).toBeTrue() + expect(component.resendCountdown).toBe(30) + // the number is locked while a code is outstanding + expect(component.phoneNumberControl?.disabled).toBeTrue() + }) + + it('counts the resend buffer down and frees the number again', fakeAsync(() => { + build() + passChallenge() + twoFactorService.sendRecoveryPhoneCode.and.returnValue( + of({ success: true, resendAfterSeconds: 2 }) + ) + component.form.get('phoneNumber')?.setValue('+441234567890') + component.sendCode() + + tick(2000) + + expect(component.resendCountdown).toBe(0) + expect(component.phoneNumberControl?.enabled).toBeTrue() + component.ngOnDestroy() + })) + + it('restarts the countdown cleanly on a second send', fakeAsync(() => { + build() + passChallenge() + twoFactorService.sendRecoveryPhoneCode.and.returnValue( + of({ success: true, resendAfterSeconds: 3 }) + ) + component.form.get('phoneNumber')?.setValue('+441234567890') + component.sendCode() + tick(3000) + + component.sendCode() + tick(1000) + + // one tick, one second gone: a leftover countdown would double the rate + expect(component.resendCountdown).toBe(2) + component.ngOnDestroy() + })) + + it('names the problem the phone field already found, rather than calling it required', () => { + build() + passChallenge() + const control = component.form.get('phoneNumber') + control?.setValue('+441234') + control?.setErrors({ invalidPhone: 'IS_POSSIBLE_LOCAL_ONLY' }) + + component.sendCode() + + expect(component.phoneErrorMessage).toBe('Phone number is too short') + expect(twoFactorService.sendRecoveryPhoneCode).not.toHaveBeenCalled() + }) + + it('still asks for a number when the field is empty', () => { + build() + passChallenge() + + component.sendCode() + + expect(component.phoneErrorMessage).toBe('Phone number is required') + }) + + it('shows the length problem the server reports for the number', () => { + build() + passChallenge() + twoFactorService.sendRecoveryPhoneCode.and.returnValue( + of({ success: false, errorCode: 'PHONE_TOO_SHORT', resendAfterSeconds: 0 }) + ) + component.form.get('phoneNumber')?.setValue('+441234') + + component.sendCode() + + expect(component.phoneErrorMessage).toBe('Phone number is too short') + expect(component.codeSent).toBeFalse() + }) + + it('asks for a code before saving', () => { + build() + passChallenge() + + component.save() + + expect(component.codeErrorMessage).toBe('A verification code is required') + expect(twoFactorService.saveRecoveryPhone).not.toHaveBeenCalled() + }) + + it('rejects a code that is not six digits', () => { + build() + passChallenge() + component.verificationCodeControl?.enable() + component.verificationCodeControl?.setValue('12345') + + component.save() + + expect(component.codeErrorMessage).toBe('Invalid verification code length') + expect(twoFactorService.saveRecoveryPhone).not.toHaveBeenCalled() + }) + + it('reports a rejected code without leaving the page', () => { + build() + passChallenge() + component.verificationCodeControl?.enable() + component.verificationCodeControl?.setValue('000000') + twoFactorService.saveRecoveryPhone.and.returnValue( + of({ success: false, errorCode: 'INVALID_CODE' }) + ) + ;(router.navigate as jasmine.Spy).calls.reset() + + component.save() + + expect(component.codeErrorMessage).toBe('Invalid verification code') + expect(router.navigate).not.toHaveBeenCalled() + }) + + it('clears the code entry when it has expired', () => { + build() + passChallenge() + component.verificationCodeControl?.enable() + component.verificationCodeControl?.setValue('123456') + component.codeSent = true + twoFactorService.saveRecoveryPhone.and.returnValue( + of({ success: false, errorCode: 'CODE_EXPIRED' }) + ) + + component.save() + + expect(component.codeSent).toBeFalse() + expect(component.verificationCodeControl?.disabled).toBeTrue() + }) + + it('asks for the challenge again when the elevation has run out', () => { + build() + passChallenge() + component.verificationCodeControl?.enable() + component.verificationCodeControl?.setValue('123456') + twoFactorService.saveRecoveryPhone.and.returnValue( + of({ success: false, errorCode: 'CHALLENGE_REQUIRED' }) + ) + dialog.open.calls.reset() + + component.save() + + expect(dialog.open).toHaveBeenCalled() + // what they typed is still there + expect(component.verificationCodeControl?.value).toBe('123456') + }) + + it('returns with the added outcome after a first number is stored', () => { + build() + passChallenge() + component.verificationCodeControl?.enable() + component.verificationCodeControl?.setValue('123456') + twoFactorService.saveRecoveryPhone.and.returnValue( + of({ success: true, maskedRecoveryPhoneNumber: '***********1234' }) + ) + ;(router.navigate as jasmine.Spy).calls.reset() + + component.save() + + expect(router.navigate).toHaveBeenCalledWith( + [ApplicationRoutes.account], + jasmine.objectContaining({ queryParams: { recoveryPhone: 'added' } }) + ) + }) + + it('returns with the updated outcome after a number is changed', () => { + build(true, status({ maskedRecoveryPhoneNumber: '***********1234' })) + passChallenge() + component.verificationCodeControl?.enable() + component.verificationCodeControl?.setValue('123456') + twoFactorService.saveRecoveryPhone.and.returnValue(of({ success: true })) + ;(router.navigate as jasmine.Spy).calls.reset() + + component.save() + + expect(router.navigate).toHaveBeenCalledWith( + [ApplicationRoutes.account], + jasmine.objectContaining({ queryParams: { recoveryPhone: 'updated' } }) + ) + }) + + it('reports a failure when saving breaks', () => { + build() + passChallenge() + component.verificationCodeControl?.enable() + component.verificationCodeControl?.setValue('123456') + twoFactorService.saveRecoveryPhone.and.returnValue( + throwError(() => new Error('boom')) + ) + ;(router.navigate as jasmine.Spy).calls.reset() + + component.save() + + expect(router.navigate).toHaveBeenCalledWith( + [ApplicationRoutes.account], + jasmine.objectContaining({ queryParams: { recoveryPhone: 'failed' } }) + ) + }) + + it('leaves without saving anything when cancelled', () => { + build() + passChallenge() + ;(router.navigate as jasmine.Spy).calls.reset() + + component.cancel() + + expect(twoFactorService.saveRecoveryPhone).not.toHaveBeenCalled() + expect(router.navigate).toHaveBeenCalledWith( + [ApplicationRoutes.account], + jasmine.objectContaining({ queryParams: {} }) + ) + }) +}) diff --git a/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts new file mode 100644 index 000000000..4a93686d4 --- /dev/null +++ b/src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts @@ -0,0 +1,480 @@ +import { Component, Inject, LOCALE_ID, OnDestroy, OnInit } from '@angular/core' +import { + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms' +import { MatDialog, MatDialogRef } from '@angular/material/dialog' +import { Router } from '@angular/router' +import { AuthChallengeComponent } from '@orcid/registry-ui' +import { Subject, Subscription, interval } from 'rxjs' +import { first, takeUntil } from 'rxjs/operators' + +import { ApplicationRoutes } from '../../../constants' +import { TogglzService } from '../../../core/togglz/togglz.service' +import { TwoFactorAuthenticationService } from '../../../core/two-factor-authentication/two-factor-authentication.service' +import { + AuthChallenge, + AuthChallengeFormData, +} from '../../../types/common.endpoint' +import { TogglzFlag } from '../../../types/config.endpoint' +import { + RecoveryPhoneErrorCode, + Status, +} from '../../../types/two-factor.endpoint' + +@Component({ + selector: 'app-recovery-phone', + templateUrl: './recovery-phone.component.html', + styleUrls: [ + './recovery-phone.component.scss', + './recovery-phone.component.scss-theme.scss', + ], + standalone: false, +}) +export class RecoveryPhoneComponent implements OnInit, OnDestroy { + private readonly $destroy = new Subject() + + loadUtils = () => import('intl-tel-input/utils') + + form: UntypedFormGroup + challengeForm: UntypedFormGroup + + loadingState = true + /** Set once the user has a number already, which turns this into a change. */ + managingExistingNumber = false + maskedRecoveryPhoneNumber: string | undefined + + codeSent = false + resendCountdown = 0 + sending = false + saving = false + + /** + * The number the outstanding code was sent to, held from the moment it was + * sent: the field can be edited again once the resend delay is over, and the + * confirmation still has to name the number the code actually went to. It is + * shown in full - it is what the user just typed into the field above, not + * anything the registry read back. + */ + sentToNumber: string | undefined + + phoneErrorMessage: string | null = null + codeErrorMessage: string | null = null + generalErrorMessage: string | null = null + + cancelLabel = $localize`:@@account.cancel:Cancel` + + /** Preselected country, taken from the region the browser reports. */ + initialCountry = 'us' + + /** Country names in the language the registry is being displayed in. */ + countryNameLocale = 'en' + + /** The phone field's own labels and search box, in the same language. */ + uiTranslations: Record | undefined + + /** + * The field reads its translations once, when it initialises, so the form + * waits for them rather than rendering an English field and updating it. + */ + translationsReady = false + + private challengeDialog: MatDialogRef | undefined + private challengePassed = false + private countdownSubscription: Subscription | undefined + + constructor( + private _router: Router, + private _dialog: MatDialog, + private _fb: UntypedFormBuilder, + private _togglz: TogglzService, + private _twoFactorAuthenticationService: TwoFactorAuthenticationService, + @Inject(LOCALE_ID) private _locale: string + ) {} + + ngOnInit(): void { + this.initialCountry = this.resolveInitialCountry() + this.loadPhoneFieldTranslations() + this.form = this._fb.group({ + phoneNumber: ['', Validators.required], + verificationCode: [ + { value: '', disabled: true }, + [Validators.required, Validators.minLength(6), Validators.maxLength(6)], + ], + }) + this.challengeForm = this._fb.group({ + password: [null, Validators.required], + twoFactorCode: [null, [Validators.minLength(6), Validators.maxLength(6)]], + twoFactorRecoveryCode: [ + null, + [Validators.minLength(10), Validators.maxLength(10)], + ], + }) + + this._togglz + .getStateOf(TogglzFlag.TWO_FACTOR_RECOVERY_PHONE) + .pipe(first(), takeUntil(this.$destroy)) + .subscribe((enabled) => { + if (!enabled) { + this.returnToAccountSettings() + return + } + this.loadStatus() + }) + } + + ngOnDestroy(): void { + this.$destroy.next() + this.$destroy.complete() + } + + get verificationCodeControl() { + return this.form.get('verificationCode') + } + + get phoneNumberControl() { + return this.form.get('phoneNumber') + } + + get title(): string { + return this.managingExistingNumber + ? $localize`:@@account.manageRecoveryPhoneTitle:Manage your recovery phone number` + : $localize`:@@account.addRecoveryPhoneTitle:Add a recovery phone number` + } + + get primaryLabel(): string { + return this.managingExistingNumber + ? $localize`:@@account.updateRecoveryPhoneNumber:Update recovery phone number` + : $localize`:@@account.addRecoveryPhoneNumber:Add recovery phone number` + } + + private get challengeDescription(): string { + return this.managingExistingNumber + ? $localize`:@@account.manageRecoveryPhoneChallenge:to manage your recovery phone number` + : $localize`:@@account.addRecoveryPhoneChallenge:to add a recovery phone number` + } + + /** + * Picks the country from the region in the browser's locale, e.g. en-GB + * gives gb. Falls back to the US when the locale carries no region. + */ + private resolveInitialCountry(): string { + const locale = navigator?.language || this._locale || '' + const region = locale.split(/[-_]/)[1] + return region && region.length === 2 ? region.toLowerCase() : 'us' + } + + /** + * The phone field ships its own translations, keyed by language rather than + * by the locale ids the registry uses, so Chinese needs mapping and anything + * it does not carry falls back to English rather than rendering blank. + */ + private loadPhoneFieldTranslations(): void { + const language = this.resolvePhoneFieldLanguage() + this.countryNameLocale = language + if (language === 'en') { + this.translationsReady = true + return + } + // The whole set is imported and one language picked out of it: a template + // path cannot be resolved through the package's export map at build time + import('intl-tel-input/locale') + .then((locales) => { + const translations = (locales as Record)[language] + if (translations) { + this.uiTranslations = translations as Record + } else { + this.countryNameLocale = 'en' + } + this.translationsReady = true + }) + .catch(() => { + // English is already the field's default, so nothing to undo + this.countryNameLocale = 'en' + this.translationsReady = true + }) + } + + private resolvePhoneFieldLanguage(): string { + const locale = (this._locale || 'en').toLowerCase() + if (locale.startsWith('zh')) { + // The field carries simplified Chinese as zh and traditional as zh-hk + return locale === 'zh-tw' || locale === 'zh-hant' ? 'zh-hk' : 'zh' + } + return locale.split('-')[0] + } + + private loadStatus(): void { + this._twoFactorAuthenticationService + .checkState() + .pipe(first(), takeUntil(this.$destroy)) + .subscribe({ + next: (status: Status) => { + if (!status?.enabled) { + // Nothing to back up when 2FA is off + this.returnToAccountSettings() + return + } + this.managingExistingNumber = !!status.maskedRecoveryPhoneNumber + this.maskedRecoveryPhoneNumber = status.maskedRecoveryPhoneNumber + this.loadingState = false + this.openAuthChallenge() + }, + error: () => this.returnToAccountSettings(), + }) + } + + /** + * The page is unusable until the challenge passes. The server enforces this + * too, so a deep link cannot get past it. + */ + private openAuthChallenge(): void { + this.challengeDialog = this._dialog.open( + AuthChallengeComponent, + { + disableClose: true, + data: { + parentForm: this.challengeForm, + actionDescription: this.challengeDescription, + } as AuthChallengeFormData, + } + ) + + this.challengeDialog.componentInstance.submitAttempt + .pipe(takeUntil(this.challengeDialog.afterClosed())) + .subscribe(() => this.submitAuthChallenge()) + + this.challengeDialog.componentInstance.cancelAttempt + .pipe(takeUntil(this.challengeDialog.afterClosed())) + .subscribe(() => this.challengeDialog?.close(false)) + + this.challengeDialog.afterClosed().subscribe((passed) => { + this.challengeDialog = undefined + if (passed) { + this.challengePassed = true + } else if (!this.challengePassed) { + this.returnToAccountSettings() + } + }) + } + + private submitAuthChallenge(): void { + const dialogRef = this.challengeDialog + if (!dialogRef) { + return + } + this._twoFactorAuthenticationService + .verifyRecoveryPhoneChallenge(this.challengeForm.value) + .pipe(first()) + .subscribe({ + next: (response: AuthChallenge) => { + if (response.success) { + dialogRef.close(true) + } else { + dialogRef.componentInstance.loading = false + dialogRef.componentInstance.processBackendResponse(response) + } + }, + error: () => { + dialogRef.componentInstance.loading = false + }, + }) + } + + sendCode(): void { + this.clearErrors() + if (this.phoneNumberControl?.invalid) { + this.phoneNumberControl.markAsTouched() + this.phoneErrorMessage = this.localPhoneErrorMessage() + return + } + + this.sending = true + this._twoFactorAuthenticationService + .sendRecoveryPhoneCode({ + phoneNumber: this.phoneNumberControl?.value, + locale: this._locale, + }) + .pipe(first()) + .subscribe({ + next: (response) => { + this.sending = false + if (response.success) { + this.codeSent = true + this.sentToNumber = this.phoneNumberControl?.value + this.verificationCodeControl?.enable() + this.startResendCountdown(response.resendAfterSeconds) + } else { + this.handleErrorCode( + response.errorCode, + response.resendAfterSeconds + ) + } + }, + error: () => { + this.sending = false + this.generalErrorMessage = $localize`:@@account.recoveryPhoneSendFailed:We could not send a verification code. Please try again.` + }, + }) + } + + save(): void { + this.clearErrors() + // A disabled control counts as valid, so check the value we actually have + const code: string = this.verificationCodeControl?.value || '' + if (!code) { + this.verificationCodeControl?.markAsTouched() + this.codeErrorMessage = $localize`:@@account.verificationCodeRequired:A verification code is required` + return + } + if (code.length !== 6) { + this.verificationCodeControl?.markAsTouched() + this.codeErrorMessage = $localize`:@@account.invalidVerificationCodeLength:Invalid verification code length` + return + } + + this.saving = true + this._twoFactorAuthenticationService + .saveRecoveryPhone({ + phoneNumber: this.phoneNumberControl?.value, + verificationCode: code, + }) + .pipe(first()) + .subscribe({ + next: (response) => { + this.saving = false + if (response.success) { + this.returnToAccountSettings( + this.managingExistingNumber ? 'updated' : 'added' + ) + } else { + this.handleErrorCode(response.errorCode) + } + }, + error: () => { + this.saving = false + this.returnToAccountSettings('failed') + }, + }) + } + + cancel(): void { + this.returnToAccountSettings() + } + + /** + * Counts down from the server's own resend buffer, so the button re-enables + * at the same moment the backend starts accepting another send. + */ + private startResendCountdown(seconds: number): void { + // Drop any countdown still running, or two of them would race and the + // button would come back before the server accepts another send + this.countdownSubscription?.unsubscribe() + this.resendCountdown = seconds + if (seconds <= 0) { + return + } + this.phoneNumberControl?.disable() + this.countdownSubscription = interval(1000) + .pipe(takeUntil(this.$destroy)) + .subscribe(() => { + this.resendCountdown-- + if (this.resendCountdown <= 0) { + this.resendCountdown = 0 + this.countdownSubscription?.unsubscribe() + this.phoneNumberControl?.enable() + } + }) + } + + /** + * The phone field validates as you type and knows why a number is wrong, so + * say which problem it is rather than sending everything to the server and + * calling it all "required". + */ + private localPhoneErrorMessage(): string { + const control = this.phoneNumberControl + if (!control?.value) { + return $localize`:@@account.recoveryPhoneRequired:Phone number is required` + } + const reason = String(control.errors?.['invalidPhone'] ?? '') + if ( + reason.includes('TOO_SHORT') || + reason.includes('LOCAL_ONLY') || + reason.includes('INVALID_LENGTH') + ) { + return $localize`:@@account.recoveryPhoneTooShort:Phone number is too short` + } + if (reason.includes('TOO_LONG')) { + return $localize`:@@account.recoveryPhoneTooLong:Phone number is too long` + } + return $localize`:@@account.recoveryPhoneInvalid:Phone number is invalid` + } + + private handleErrorCode( + errorCode: RecoveryPhoneErrorCode | undefined, + resendAfterSeconds = 0 + ): void { + switch (errorCode) { + case 'PHONE_TOO_SHORT': + this.phoneErrorMessage = $localize`:@@account.recoveryPhoneTooShort:Phone number is too short` + break + case 'PHONE_TOO_LONG': + this.phoneErrorMessage = $localize`:@@account.recoveryPhoneTooLong:Phone number is too long` + break + case 'INVALID_PHONE_NUMBER': + this.phoneErrorMessage = $localize`:@@account.recoveryPhoneInvalid:Phone number is invalid` + break + case 'PHONE_MISMATCH': + this.codeErrorMessage = $localize`:@@account.recoveryPhoneMismatch:This code was sent to a different number. Send a new code to this number.` + break + case 'RESEND_TOO_SOON': + this.startResendCountdown(resendAfterSeconds) + break + case 'INVALID_CODE': + this.codeErrorMessage = $localize`:@@account.invalidVerificationCode:Invalid verification code` + break + case 'CODE_EXPIRED': + case 'TOO_MANY_ATTEMPTS': + this.resetCodeEntry() + this.codeErrorMessage = $localize`:@@account.verificationCodeExpired:That code is no longer valid. Send a new code.` + break + case 'CHALLENGE_REQUIRED': + // The elevation ran out mid form; ask again and keep what they typed + this.challengePassed = false + this.openAuthChallenge() + break + case '2FA_DISABLED': + case 'FEATURE_DISABLED': + this.returnToAccountSettings('failed') + break + default: + this.generalErrorMessage = $localize`:@@account.recoveryPhoneGenericError:Something went wrong. Please try again.` + } + } + + private resetCodeEntry(): void { + this.codeSent = false + this.sentToNumber = undefined + this.resendCountdown = 0 + this.verificationCodeControl?.reset('') + this.verificationCodeControl?.disable() + this.phoneNumberControl?.enable() + } + + private clearErrors(): void { + this.phoneErrorMessage = null + this.codeErrorMessage = null + this.generalErrorMessage = null + } + + private returnToAccountSettings( + outcome?: 'added' | 'updated' | 'failed' + ): void { + this._router.navigate([ApplicationRoutes.account], { + queryParams: outcome ? { recoveryPhone: outcome } : {}, + fragment: '2FA', + }) + } +} diff --git a/src/app/two-factor-recovery-phone/two-factor-recovery-phone-routing.module.ts b/src/app/two-factor-recovery-phone/two-factor-recovery-phone-routing.module.ts new file mode 100644 index 000000000..bf8cbdae1 --- /dev/null +++ b/src/app/two-factor-recovery-phone/two-factor-recovery-phone-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' +import { RecoveryPhoneComponent } from './pages/recovery-phone/recovery-phone.component' + +const routes: Routes = [ + { + path: '', + component: RecoveryPhoneComponent, + }, +] + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class TwoFactorRecoveryPhoneRoutingModule {} diff --git a/src/app/two-factor-recovery-phone/two-factor-recovery-phone.module.ts b/src/app/two-factor-recovery-phone/two-factor-recovery-phone.module.ts new file mode 100644 index 000000000..f509852fc --- /dev/null +++ b/src/app/two-factor-recovery-phone/two-factor-recovery-phone.module.ts @@ -0,0 +1,32 @@ +import { CommonModule } from '@angular/common' +import { NgModule } from '@angular/core' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { MatButtonModule } from '@angular/material/button' +import { MatDialogModule } from '@angular/material/dialog' +import { MatFormFieldModule } from '@angular/material/form-field' +import { MatIconModule } from '@angular/material/icon' +import { MatInputModule } from '@angular/material/input' +import IntlTelInput from '@intl-tel-input/angular' +import { AlertMessageComponent, OrcidStepViewComponent } from '@orcid/ui' + +import { RecoveryPhoneComponent } from './pages/recovery-phone/recovery-phone.component' +import { TwoFactorRecoveryPhoneRoutingModule } from './two-factor-recovery-phone-routing.module' + +@NgModule({ + declarations: [RecoveryPhoneComponent], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + TwoFactorRecoveryPhoneRoutingModule, + MatButtonModule, + MatDialogModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + IntlTelInput, + AlertMessageComponent, + OrcidStepViewComponent, + ], +}) +export class TwoFactorRecoveryPhoneModule {} diff --git a/src/app/types/config.endpoint.ts b/src/app/types/config.endpoint.ts index 8c2549b5e..2eb67d3d6 100644 --- a/src/app/types/config.endpoint.ts +++ b/src/app/types/config.endpoint.ts @@ -17,6 +17,7 @@ export const TogglzFlag = { PERMISSION_NOTIFICATIONS: 'PERMISSION_NOTIFICATIONS', SEARCH_AND_LINK_WIZARD_WITH_CERTIFIED_AND_FEATURED_LINKS: 'SEARCH_AND_LINK_WIZARD_WITH_CERTIFIED_AND_FEATURED_LINKS', + TWO_FACTOR_RECOVERY_PHONE: 'TWO_FACTOR_RECOVERY_PHONE', /** Real User Monitoring / session recording (supports percentage 0–100 via FEATURE_PERCENTAGE). */ RUM: 'RUM', } as const diff --git a/src/app/types/two-factor.endpoint.ts b/src/app/types/two-factor.endpoint.ts index 6266007f2..407a72420 100644 --- a/src/app/types/two-factor.endpoint.ts +++ b/src/app/types/two-factor.endpoint.ts @@ -22,4 +22,51 @@ export interface Status extends AuthChallenge { enabled: boolean twoFactorCreationDate: ExtendedDate recoveryCodeCreationDate: ExtendedDate + // Only ever the last four digits: the registry cannot read the number back + maskedRecoveryPhoneNumber?: string + recoveryPhoneCreationDate?: ExtendedDate + recoveryPhoneLastModifiedDate?: ExtendedDate + recoveryPhoneModified?: boolean +} + +export type RecoveryPhoneErrorCode = + | 'INVALID_PHONE_NUMBER' + | 'PHONE_TOO_SHORT' + | 'PHONE_TOO_LONG' + | 'RESEND_TOO_SOON' + | 'SMS_SEND_FAILED' + | 'SMS_RECIPIENT_NOT_ALLOWED' + | 'SMS_PROVIDER_NOT_CONFIGURED' + | 'CODE_STORAGE_UNAVAILABLE' + | 'INVALID_CODE' + | 'CODE_EXPIRED' + | 'TOO_MANY_ATTEMPTS' + | 'PHONE_MISMATCH' + | 'CHALLENGE_REQUIRED' + | '2FA_DISABLED' + | 'FEATURE_DISABLED' + +export interface RecoveryPhoneSendCodeRequest { + phoneNumber: string + locale?: string +} + +export interface RecoveryPhoneSendCodeResponse { + success: boolean + errorCode?: RecoveryPhoneErrorCode + resendAfterSeconds: number +} + +export interface RecoveryPhoneSaveRequest { + phoneNumber: string + verificationCode: string +} + +export interface RecoveryPhoneSaveResponse { + success: boolean + errorCode?: RecoveryPhoneErrorCode + maskedRecoveryPhoneNumber?: string + recoveryPhoneCreationDate?: ExtendedDate + recoveryPhoneLastModifiedDate?: ExtendedDate + recoveryPhoneModified?: boolean } diff --git a/src/locale/messages.lr.xlf b/src/locale/messages.lr.xlf index 7a4380983..92e8f24ac 100644 --- a/src/locale/messages.lr.xlf +++ b/src/locale/messages.lr.xlf @@ -333,7 +333,7 @@ Verify my account and continue projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 286,288 + 287,289 LR @@ -341,7 +341,7 @@ Cancel account verification projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 296,298 + 298,300 LR @@ -1554,11 +1554,35 @@ LR + + Your recovery phone number has been added + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 7 + + LR + + + Your recovery phone number has been updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 11 + + LR + + + Your recovery phone number was not updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 18,20 + + LR + Two-factor authentication has been disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 4,6 + 24,26 LR @@ -1566,7 +1590,7 @@ Two-factor authentication was not disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 10,12 + 30,32 LR @@ -1574,7 +1598,7 @@ Add extra security to your ORCID account by enabling two-factor authentication (2FA). src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 16,19 + 36,39 LR @@ -1586,7 +1610,11 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 26,28 + 46,48 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 203,205 src/app/two-factor-setup/components/two-factor-recovery-codes/two-factor-recovery-codes.component.html @@ -1598,7 +1626,7 @@ Enable two-factor authentication src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 37,39 + 57,59 src/app/two-factor-setup/components/two-factor-enable/two-factor-enable.component.html @@ -1618,7 +1646,7 @@ Sign in with 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 44 + 64 LR @@ -1626,7 +1654,7 @@ Use your authentication app to generate a verification code whenever you sign in to ORCID. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 48,51 + 68,71 LR @@ -1634,7 +1662,31 @@ Authentication app src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 61,63 + 81,83 + + LR + + + Enabled: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 86 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 137 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 177 + + LR + + + 2FA backup + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 98 LR @@ -1642,7 +1694,7 @@ Account recovery src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 74 + 100 LR @@ -1650,7 +1702,43 @@ Recover access to your ORCID account if you can't use your authentication app. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 78,81 + 105,108 + + LR + + + Recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 124 + + LR + + + Modified: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 135 + + LR + + + Manage recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 151 + + LR + + + Add recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 155 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 151 LR @@ -1658,7 +1746,19 @@ 2FA recovery codes src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 91,93 + 172,174 + + LR + + + Disable two-factor authentication + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 188,190 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 225,227 LR @@ -1666,7 +1766,7 @@ You can disable two-factor authentication at any time. Turning 2FA off will reset any account recovery options you have set up. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 103,106 + 192,195 LR @@ -1678,15 +1778,7 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 113,115 - - LR - - - Disable two-factor authentication - - src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 123,125 + 214,216 LR @@ -1694,7 +1786,7 @@ to disable 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.ts - 32 + 44 LR @@ -6431,7 +6523,7 @@ ORCID src/app/constants.ts - 123 + 124 LR @@ -6439,11 +6531,15 @@ 2FA - ORCID src/app/constants.ts - 124 + 125 src/app/constants.ts - 125 + 126 + + + src/app/constants.ts + 127 LR @@ -6451,7 +6547,7 @@ Institutional linking - ORCID src/app/constants.ts - 126 + 128 LR @@ -6459,7 +6555,7 @@ Social linking - ORCID src/app/constants.ts - 127 + 129 LR @@ -6467,7 +6563,7 @@ Institutional sign in - ORCID src/app/constants.ts - 128 + 130 LR @@ -6475,7 +6571,7 @@ Notifications inbox - ORCID src/app/constants.ts - 129 + 131 LR @@ -6483,15 +6579,15 @@ Sign in - ORCID src/app/constants.ts - 130 + 132 src/app/constants.ts - 131 + 133 src/app/constants.ts - 137 + 139 LR @@ -6499,7 +6595,7 @@ Oauth - ORCID src/app/constants.ts - 132 + 134 LR @@ -6507,7 +6603,7 @@ Search - ORCID src/app/constants.ts - 133 + 135 LR @@ -6515,7 +6611,7 @@ Account reactivation - ORCID src/app/constants.ts - 134 + 136 LR @@ -6523,7 +6619,7 @@ Reset password - ORCID src/app/constants.ts - 135 + 137 LR @@ -6531,7 +6627,7 @@ Register - ORCID src/app/constants.ts - 136 + 138 LR @@ -6539,7 +6635,7 @@ Account settings - ORCID src/app/constants.ts - 138 + 140 LR @@ -6547,7 +6643,7 @@ Deactivate account - ORCID src/app/constants.ts - 139 + 141 LR @@ -6555,7 +6651,7 @@ Trusted parties - ORCID src/app/constants.ts - 140 + 142 LR @@ -6563,7 +6659,7 @@ Reset password - ORCID src/app/constants.ts - 141 + 143 LR @@ -6571,7 +6667,7 @@ Self Service - ORCID src/app/constants.ts - 142 + 144 LR @@ -6579,7 +6675,7 @@ Developer tools - ORCID src/app/constants.ts - 143 + 145 LR @@ -6587,11 +6683,11 @@ Record corrections - ORCID src/app/constants.ts - 145 + 147 src/app/constants.ts - 146 + 148 LR @@ -6599,7 +6695,7 @@ - ORCID src/app/constants.ts - 150 + 152 LR @@ -6607,7 +6703,7 @@ - My ORCID src/app/constants.ts - 151 + 153 LR @@ -6615,7 +6711,7 @@ Manage employment dialog src/app/constants.ts - 376 + 378 LR @@ -6623,7 +6719,7 @@ Manage education dialog src/app/constants.ts - 378 + 380 LR @@ -6631,7 +6727,7 @@ Manage qualification dialog src/app/constants.ts - 380 + 382 LR @@ -6639,7 +6735,7 @@ Manage distinction dialog src/app/constants.ts - 382 + 384 LR @@ -6647,7 +6743,7 @@ Manage invited position dialog src/app/constants.ts - 384 + 386 LR @@ -6655,7 +6751,7 @@ Manage membership dialog src/app/constants.ts - 386 + 388 LR @@ -6663,7 +6759,7 @@ Manage service dialog src/app/constants.ts - 388 + 390 LR @@ -6671,7 +6767,7 @@ Manage editorial service dialog src/app/constants.ts - 390 + 392 LR @@ -6679,7 +6775,7 @@ Manage bibtex dialog src/app/constants.ts - 393 + 395 LR @@ -6687,7 +6783,7 @@ Manage external identifier dialog src/app/constants.ts - 395 + 397 LR @@ -6695,7 +6791,7 @@ Manage your names dialog src/app/constants.ts - 397 + 399 LR @@ -6703,7 +6799,7 @@ Manage your biography dialog src/app/constants.ts - 399 + 401 LR @@ -6711,7 +6807,7 @@ Manage your emails dialog src/app/constants.ts - 401 + 403 LR @@ -6719,7 +6815,7 @@ Manage your countries dialog src/app/constants.ts - 403 + 405 LR @@ -6727,7 +6823,7 @@ Manage your keywords dialog src/app/constants.ts - 405 + 407 LR @@ -6735,7 +6831,7 @@ Manage your other IDs dialog src/app/constants.ts - 407 + 409 LR @@ -6743,7 +6839,7 @@ Manage your websites & social links dialog src/app/constants.ts - 409 + 411 LR @@ -6751,7 +6847,7 @@ Manage funding dialog src/app/constants.ts - 411 + 413 LR @@ -6759,7 +6855,7 @@ Manage funding search dialog src/app/constants.ts - 413 + 415 LR @@ -6767,7 +6863,7 @@ Manage work dialog src/app/constants.ts - 415 + 417 LR @@ -6775,7 +6871,7 @@ Manage work search dialog src/app/constants.ts - 417 + 419 LR @@ -6783,7 +6879,7 @@ Manage peer review dialog src/app/constants.ts - 419 + 421 LR @@ -19415,6 +19511,266 @@ LR + + Your recovery phone number can be used to access your ORCID account if you don't have your authentication app or recovery codes. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 20,23 + + LR + + + Your recovery phone number can only be used once. Using it will disable two-factor authentication and reset your 2FA backup options. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 27,30 + + LR + + + Your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 37,39 + + LR + + + Your current recovery phone number is + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 44 + + LR + + + Select a country or location and enter your phone number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 55,57 + + LR + + + By entering my phone number and clicking 'Send verification code', I consent to receive an automated one-time verification code from ORCID at the number provided. Message and data rates may apply. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 87,91 + + LR + + + Terms of use + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 98 + + LR + + + Privacy policy + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 106 + + LR + + + Send verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 120,122 + + LR + + + Verification code sent to + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 126 + + LR + + + Resend in + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 130 + + LR + + + seconds + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 134 + + LR + + + Verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 150 + + LR + + + Enter the 6-digit verification code sent to your device + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 175 + + LR + + + Cancel + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 68 + + LR + + + Manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 144 + + LR + + + Add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 145 + + LR + + + Update recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 150 + + LR + + + to manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 156 + + LR + + + to add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 157 + + LR + + + We could not send a verification code. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 319 + + LR + + + A verification code is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 330 + + LR + + + Invalid verification code length + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 335 + + LR + + + Phone number is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 401 + + LR + + + Phone number is too short + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 409 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 433 + + LR + + + Phone number is too long + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 412 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 436 + + LR + + + Phone number is invalid + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 414 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 439 + + LR + + + This code was sent to a different number. Send a new code to this number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 442 + + LR + + + Invalid verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 448 + + LR + + + That code is no longer valid. Send a new code. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 453 + + LR + + + Something went wrong. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 465 + + LR + Step 1 of 2 - Authentication app diff --git a/src/locale/messages.rl.xlf b/src/locale/messages.rl.xlf index ea204007a..1bec4f5bd 100644 --- a/src/locale/messages.rl.xlf +++ b/src/locale/messages.rl.xlf @@ -333,7 +333,7 @@ Verify my account and continue projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 286,288 + 287,289 RL @@ -341,7 +341,7 @@ Cancel account verification projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 296,298 + 298,300 RL @@ -1554,11 +1554,35 @@ RL + + Your recovery phone number has been added + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 7 + + RL + + + Your recovery phone number has been updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 11 + + RL + + + Your recovery phone number was not updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 18,20 + + RL + Two-factor authentication has been disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 4,6 + 24,26 RL @@ -1566,7 +1590,7 @@ Two-factor authentication was not disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 10,12 + 30,32 RL @@ -1574,7 +1598,7 @@ Add extra security to your ORCID account by enabling two-factor authentication (2FA). src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 16,19 + 36,39 RL @@ -1586,7 +1610,11 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 26,28 + 46,48 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 203,205 src/app/two-factor-setup/components/two-factor-recovery-codes/two-factor-recovery-codes.component.html @@ -1598,7 +1626,7 @@ Enable two-factor authentication src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 37,39 + 57,59 src/app/two-factor-setup/components/two-factor-enable/two-factor-enable.component.html @@ -1618,7 +1646,7 @@ Sign in with 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 44 + 64 RL @@ -1626,7 +1654,7 @@ Use your authentication app to generate a verification code whenever you sign in to ORCID. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 48,51 + 68,71 RL @@ -1634,7 +1662,31 @@ Authentication app src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 61,63 + 81,83 + + RL + + + Enabled: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 86 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 137 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 177 + + RL + + + 2FA backup + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 98 RL @@ -1642,7 +1694,7 @@ Account recovery src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 74 + 100 RL @@ -1650,7 +1702,43 @@ Recover access to your ORCID account if you can't use your authentication app. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 78,81 + 105,108 + + RL + + + Recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 124 + + RL + + + Modified: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 135 + + RL + + + Manage recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 151 + + RL + + + Add recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 155 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 151 RL @@ -1658,7 +1746,19 @@ 2FA recovery codes src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 91,93 + 172,174 + + RL + + + Disable two-factor authentication + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 188,190 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 225,227 RL @@ -1666,7 +1766,7 @@ You can disable two-factor authentication at any time. Turning 2FA off will reset any account recovery options you have set up. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 103,106 + 192,195 RL @@ -1678,15 +1778,7 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 113,115 - - RL - - - Disable two-factor authentication - - src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 123,125 + 214,216 RL @@ -1694,7 +1786,7 @@ to disable 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.ts - 32 + 44 RL @@ -6431,7 +6523,7 @@ ORCID src/app/constants.ts - 123 + 124 RL @@ -6439,11 +6531,15 @@ 2FA - ORCID src/app/constants.ts - 124 + 125 src/app/constants.ts - 125 + 126 + + + src/app/constants.ts + 127 RL @@ -6451,7 +6547,7 @@ Institutional linking - ORCID src/app/constants.ts - 126 + 128 RL @@ -6459,7 +6555,7 @@ Social linking - ORCID src/app/constants.ts - 127 + 129 RL @@ -6467,7 +6563,7 @@ Institutional sign in - ORCID src/app/constants.ts - 128 + 130 RL @@ -6475,7 +6571,7 @@ Notifications inbox - ORCID src/app/constants.ts - 129 + 131 RL @@ -6483,15 +6579,15 @@ Sign in - ORCID src/app/constants.ts - 130 + 132 src/app/constants.ts - 131 + 133 src/app/constants.ts - 137 + 139 RL @@ -6499,7 +6595,7 @@ Oauth - ORCID src/app/constants.ts - 132 + 134 RL @@ -6507,7 +6603,7 @@ Search - ORCID src/app/constants.ts - 133 + 135 RL @@ -6515,7 +6611,7 @@ Account reactivation - ORCID src/app/constants.ts - 134 + 136 RL @@ -6523,7 +6619,7 @@ Reset password - ORCID src/app/constants.ts - 135 + 137 RL @@ -6531,7 +6627,7 @@ Register - ORCID src/app/constants.ts - 136 + 138 RL @@ -6539,7 +6635,7 @@ Account settings - ORCID src/app/constants.ts - 138 + 140 RL @@ -6547,7 +6643,7 @@ Deactivate account - ORCID src/app/constants.ts - 139 + 141 RL @@ -6555,7 +6651,7 @@ Trusted parties - ORCID src/app/constants.ts - 140 + 142 RL @@ -6563,7 +6659,7 @@ Reset password - ORCID src/app/constants.ts - 141 + 143 RL @@ -6571,7 +6667,7 @@ Self Service - ORCID src/app/constants.ts - 142 + 144 RL @@ -6579,7 +6675,7 @@ Developer tools - ORCID src/app/constants.ts - 143 + 145 RL @@ -6587,11 +6683,11 @@ Record corrections - ORCID src/app/constants.ts - 145 + 147 src/app/constants.ts - 146 + 148 RL @@ -6599,7 +6695,7 @@ - ORCID src/app/constants.ts - 150 + 152 RL @@ -6607,7 +6703,7 @@ - My ORCID src/app/constants.ts - 151 + 153 RL @@ -6615,7 +6711,7 @@ Manage employment dialog src/app/constants.ts - 376 + 378 RL @@ -6623,7 +6719,7 @@ Manage education dialog src/app/constants.ts - 378 + 380 RL @@ -6631,7 +6727,7 @@ Manage qualification dialog src/app/constants.ts - 380 + 382 RL @@ -6639,7 +6735,7 @@ Manage distinction dialog src/app/constants.ts - 382 + 384 RL @@ -6647,7 +6743,7 @@ Manage invited position dialog src/app/constants.ts - 384 + 386 RL @@ -6655,7 +6751,7 @@ Manage membership dialog src/app/constants.ts - 386 + 388 RL @@ -6663,7 +6759,7 @@ Manage service dialog src/app/constants.ts - 388 + 390 RL @@ -6671,7 +6767,7 @@ Manage editorial service dialog src/app/constants.ts - 390 + 392 RL @@ -6679,7 +6775,7 @@ Manage bibtex dialog src/app/constants.ts - 393 + 395 RL @@ -6687,7 +6783,7 @@ Manage external identifier dialog src/app/constants.ts - 395 + 397 RL @@ -6695,7 +6791,7 @@ Manage your names dialog src/app/constants.ts - 397 + 399 RL @@ -6703,7 +6799,7 @@ Manage your biography dialog src/app/constants.ts - 399 + 401 RL @@ -6711,7 +6807,7 @@ Manage your emails dialog src/app/constants.ts - 401 + 403 RL @@ -6719,7 +6815,7 @@ Manage your countries dialog src/app/constants.ts - 403 + 405 RL @@ -6727,7 +6823,7 @@ Manage your keywords dialog src/app/constants.ts - 405 + 407 RL @@ -6735,7 +6831,7 @@ Manage your other IDs dialog src/app/constants.ts - 407 + 409 RL @@ -6743,7 +6839,7 @@ Manage your websites & social links dialog src/app/constants.ts - 409 + 411 RL @@ -6751,7 +6847,7 @@ Manage funding dialog src/app/constants.ts - 411 + 413 RL @@ -6759,7 +6855,7 @@ Manage funding search dialog src/app/constants.ts - 413 + 415 RL @@ -6767,7 +6863,7 @@ Manage work dialog src/app/constants.ts - 415 + 417 RL @@ -6775,7 +6871,7 @@ Manage work search dialog src/app/constants.ts - 417 + 419 RL @@ -6783,7 +6879,7 @@ Manage peer review dialog src/app/constants.ts - 419 + 421 RL @@ -19415,6 +19511,266 @@ RL + + Your recovery phone number can be used to access your ORCID account if you don't have your authentication app or recovery codes. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 20,23 + + RL + + + Your recovery phone number can only be used once. Using it will disable two-factor authentication and reset your 2FA backup options. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 27,30 + + RL + + + Your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 37,39 + + RL + + + Your current recovery phone number is + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 44 + + RL + + + Select a country or location and enter your phone number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 55,57 + + RL + + + By entering my phone number and clicking 'Send verification code', I consent to receive an automated one-time verification code from ORCID at the number provided. Message and data rates may apply. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 87,91 + + RL + + + Terms of use + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 98 + + RL + + + Privacy policy + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 106 + + RL + + + Send verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 120,122 + + RL + + + Verification code sent to + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 126 + + RL + + + Resend in + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 130 + + RL + + + seconds + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 134 + + RL + + + Verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 150 + + RL + + + Enter the 6-digit verification code sent to your device + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 175 + + RL + + + Cancel + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 68 + + RL + + + Manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 144 + + RL + + + Add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 145 + + RL + + + Update recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 150 + + RL + + + to manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 156 + + RL + + + to add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 157 + + RL + + + We could not send a verification code. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 319 + + RL + + + A verification code is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 330 + + RL + + + Invalid verification code length + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 335 + + RL + + + Phone number is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 401 + + RL + + + Phone number is too short + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 409 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 433 + + RL + + + Phone number is too long + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 412 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 436 + + RL + + + Phone number is invalid + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 414 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 439 + + RL + + + This code was sent to a different number. Send a new code to this number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 442 + + RL + + + Invalid verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 448 + + RL + + + That code is no longer valid. Send a new code. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 453 + + RL + + + Something went wrong. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 465 + + RL + Step 1 of 2 - Authentication app diff --git a/src/locale/messages.xlf b/src/locale/messages.xlf index 2844f2434..408fee25a 100644 --- a/src/locale/messages.xlf +++ b/src/locale/messages.xlf @@ -309,14 +309,14 @@ Verify my account and continue projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 286,288 + 287,289 Cancel account verification projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 296,298 + 298,300 @@ -1419,25 +1419,46 @@ 51 + + Your recovery phone number has been added + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 7 + + + + Your recovery phone number has been updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 11 + + + + Your recovery phone number was not updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 18,20 + + Two-factor authentication has been disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 4,6 + 24,26 Two-factor authentication was not disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 10,12 + 30,32 Add extra security to your ORCID account by enabling two-factor authentication (2FA). src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 16,19 + 36,39 @@ -1448,7 +1469,11 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 26,28 + 46,48 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 203,205 src/app/two-factor-setup/components/two-factor-recovery-codes/two-factor-recovery-codes.component.html @@ -1459,7 +1484,7 @@ Enable two-factor authentication src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 37,39 + 57,59 src/app/two-factor-setup/components/two-factor-enable/two-factor-enable.component.html @@ -1478,49 +1503,114 @@ Sign in with 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 44 + 64 Use your authentication app to generate a verification code whenever you sign in to ORCID. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 48,51 + 68,71 Authentication app src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 61,63 + 81,83 + + + + Enabled: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 86 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 137 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 177 + + + + 2FA backup + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 98 Account recovery src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 74 + 100 Recover access to your ORCID account if you can't use your authentication app. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 78,81 + 105,108 + + + + Recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 124 + + + + Modified: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 135 + + + + Manage recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 151 + + + + Add recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 155 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 151 2FA recovery codes src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 91,93 + 172,174 + + + + Disable two-factor authentication + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 188,190 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 225,227 You can disable two-factor authentication at any time. Turning 2FA off will reset any account recovery options you have set up. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 103,106 + 192,195 @@ -1531,21 +1621,14 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 113,115 - - - - Disable two-factor authentication - - src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 123,125 + 214,216 to disable 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.ts - 32 + 44 @@ -5833,317 +5916,321 @@ ORCID src/app/constants.ts - 123 + 124 2FA - ORCID src/app/constants.ts - 124 + 125 src/app/constants.ts - 125 + 126 + + + src/app/constants.ts + 127 Institutional linking - ORCID src/app/constants.ts - 126 + 128 Social linking - ORCID src/app/constants.ts - 127 + 129 Institutional sign in - ORCID src/app/constants.ts - 128 + 130 Notifications inbox - ORCID src/app/constants.ts - 129 + 131 Sign in - ORCID src/app/constants.ts - 130 + 132 src/app/constants.ts - 131 + 133 src/app/constants.ts - 137 + 139 Oauth - ORCID src/app/constants.ts - 132 + 134 Search - ORCID src/app/constants.ts - 133 + 135 Account reactivation - ORCID src/app/constants.ts - 134 + 136 Reset password - ORCID src/app/constants.ts - 135 + 137 Register - ORCID src/app/constants.ts - 136 + 138 Account settings - ORCID src/app/constants.ts - 138 + 140 Deactivate account - ORCID src/app/constants.ts - 139 + 141 Trusted parties - ORCID src/app/constants.ts - 140 + 142 Reset password - ORCID src/app/constants.ts - 141 + 143 Self Service - ORCID src/app/constants.ts - 142 + 144 Developer tools - ORCID src/app/constants.ts - 143 + 145 Record corrections - ORCID src/app/constants.ts - 145 + 147 src/app/constants.ts - 146 + 148 - ORCID src/app/constants.ts - 150 + 152 - My ORCID src/app/constants.ts - 151 + 153 Manage employment dialog src/app/constants.ts - 376 + 378 Manage education dialog src/app/constants.ts - 378 + 380 Manage qualification dialog src/app/constants.ts - 380 + 382 Manage distinction dialog src/app/constants.ts - 382 + 384 Manage invited position dialog src/app/constants.ts - 384 + 386 Manage membership dialog src/app/constants.ts - 386 + 388 Manage service dialog src/app/constants.ts - 388 + 390 Manage editorial service dialog src/app/constants.ts - 390 + 392 Manage bibtex dialog src/app/constants.ts - 393 + 395 Manage external identifier dialog src/app/constants.ts - 395 + 397 Manage your names dialog src/app/constants.ts - 397 + 399 Manage your biography dialog src/app/constants.ts - 399 + 401 Manage your emails dialog src/app/constants.ts - 401 + 403 Manage your countries dialog src/app/constants.ts - 403 + 405 Manage your keywords dialog src/app/constants.ts - 405 + 407 Manage your other IDs dialog src/app/constants.ts - 407 + 409 Manage your websites & social links dialog src/app/constants.ts - 409 + 411 Manage funding dialog src/app/constants.ts - 411 + 413 Manage funding search dialog src/app/constants.ts - 413 + 415 Manage work dialog src/app/constants.ts - 415 + 417 Manage work search dialog src/app/constants.ts - 417 + 419 Manage peer review dialog src/app/constants.ts - 419 + 421 @@ -17398,6 +17485,235 @@ 54 + + Your recovery phone number can be used to access your ORCID account if you don't have your authentication app or recovery codes. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 20,23 + + + + Your recovery phone number can only be used once. Using it will disable two-factor authentication and reset your 2FA backup options. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 27,30 + + + + Your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 37,39 + + + + Your current recovery phone number is + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 44 + + + + Select a country or location and enter your phone number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 55,57 + + + + By entering my phone number and clicking 'Send verification code', I consent to receive an automated one-time verification code from ORCID at the number provided. Message and data rates may apply. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 87,91 + + + + Terms of use + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 98 + + + + Privacy policy + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 106 + + + + Send verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 120,122 + + + + Verification code sent to + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 126 + + + + Resend in + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 130 + + + + seconds + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 134 + + + + Verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 150 + + + + Enter the 6-digit verification code sent to your device + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 175 + + + + Cancel + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 68 + + + + Manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 144 + + + + Add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 145 + + + + Update recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 150 + + + + to manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 156 + + + + to add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 157 + + + + We could not send a verification code. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 319 + + + + A verification code is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 330 + + + + Invalid verification code length + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 335 + + + + Phone number is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 401 + + + + Phone number is too short + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 409 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 433 + + + + Phone number is too long + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 412 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 436 + + + + Phone number is invalid + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 414 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 439 + + + + This code was sent to a different number. Send a new code to this number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 442 + + + + Invalid verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 448 + + + + That code is no longer valid. Send a new code. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 453 + + + + Something went wrong. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 465 + + Step 1 of 2 - Authentication app diff --git a/src/locale/messages.xx.xlf b/src/locale/messages.xx.xlf index f7fd69e6f..c1402ccbc 100644 --- a/src/locale/messages.xx.xlf +++ b/src/locale/messages.xx.xlf @@ -333,7 +333,7 @@ Verify my account and continue projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 286,288 + 287,289 X @@ -341,7 +341,7 @@ Cancel account verification projects/orcid-registry-ui/src/lib/components/auth-challenge/auth-challenge.component.html - 296,298 + 298,300 X @@ -1554,11 +1554,35 @@ X + + Your recovery phone number has been added + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 7 + + X + + + Your recovery phone number has been updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 11 + + X + + + Your recovery phone number was not updated + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 18,20 + + X + Two-factor authentication has been disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 4,6 + 24,26 X @@ -1566,7 +1590,7 @@ Two-factor authentication was not disabled src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 10,12 + 30,32 X @@ -1574,7 +1598,7 @@ Add extra security to your ORCID account by enabling two-factor authentication (2FA). src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 16,19 + 36,39 X @@ -1586,7 +1610,11 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 26,28 + 46,48 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 203,205 src/app/two-factor-setup/components/two-factor-recovery-codes/two-factor-recovery-codes.component.html @@ -1598,7 +1626,7 @@ Enable two-factor authentication src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 37,39 + 57,59 src/app/two-factor-setup/components/two-factor-enable/two-factor-enable.component.html @@ -1618,7 +1646,7 @@ Sign in with 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 44 + 64 X @@ -1626,7 +1654,7 @@ Use your authentication app to generate a verification code whenever you sign in to ORCID. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 48,51 + 68,71 X @@ -1634,7 +1662,31 @@ Authentication app src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 61,63 + 81,83 + + X + + + Enabled: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 86 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 137 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 177 + + X + + + 2FA backup + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 98 X @@ -1642,7 +1694,7 @@ Account recovery src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 74 + 100 X @@ -1650,7 +1702,43 @@ Recover access to your ORCID account if you can't use your authentication app. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 78,81 + 105,108 + + X + + + Recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 124 + + X + + + Modified: + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 135 + + X + + + Manage recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 151 + + X + + + Add recovery phone number + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 155 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 151 X @@ -1658,7 +1746,19 @@ 2FA recovery codes src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 91,93 + 172,174 + + X + + + Disable two-factor authentication + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 188,190 + + + src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html + 225,227 X @@ -1666,7 +1766,7 @@ You can disable two-factor authentication at any time. Turning 2FA off will reset any account recovery options you have set up. src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 103,106 + 192,195 X @@ -1678,15 +1778,7 @@ src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 113,115 - - X - - - Disable two-factor authentication - - src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.html - 123,125 + 214,216 X @@ -1694,7 +1786,7 @@ to disable 2FA src/app/account-settings/components/settings-security-two-factor-auth/settings-security-two-factor-auth.component.ts - 32 + 44 X @@ -6431,7 +6523,7 @@ ORCID src/app/constants.ts - 123 + 124 X @@ -6439,11 +6531,15 @@ 2FA - ORCID src/app/constants.ts - 124 + 125 src/app/constants.ts - 125 + 126 + + + src/app/constants.ts + 127 X @@ -6451,7 +6547,7 @@ Institutional linking - ORCID src/app/constants.ts - 126 + 128 X @@ -6459,7 +6555,7 @@ Social linking - ORCID src/app/constants.ts - 127 + 129 X @@ -6467,7 +6563,7 @@ Institutional sign in - ORCID src/app/constants.ts - 128 + 130 X @@ -6475,7 +6571,7 @@ Notifications inbox - ORCID src/app/constants.ts - 129 + 131 X @@ -6483,15 +6579,15 @@ Sign in - ORCID src/app/constants.ts - 130 + 132 src/app/constants.ts - 131 + 133 src/app/constants.ts - 137 + 139 X @@ -6499,7 +6595,7 @@ Oauth - ORCID src/app/constants.ts - 132 + 134 X @@ -6507,7 +6603,7 @@ Search - ORCID src/app/constants.ts - 133 + 135 X @@ -6515,7 +6611,7 @@ Account reactivation - ORCID src/app/constants.ts - 134 + 136 X @@ -6523,7 +6619,7 @@ Reset password - ORCID src/app/constants.ts - 135 + 137 X @@ -6531,7 +6627,7 @@ Register - ORCID src/app/constants.ts - 136 + 138 X @@ -6539,7 +6635,7 @@ Account settings - ORCID src/app/constants.ts - 138 + 140 X @@ -6547,7 +6643,7 @@ Deactivate account - ORCID src/app/constants.ts - 139 + 141 X @@ -6555,7 +6651,7 @@ Trusted parties - ORCID src/app/constants.ts - 140 + 142 X @@ -6563,7 +6659,7 @@ Reset password - ORCID src/app/constants.ts - 141 + 143 X @@ -6571,7 +6667,7 @@ Self Service - ORCID src/app/constants.ts - 142 + 144 X @@ -6579,7 +6675,7 @@ Developer tools - ORCID src/app/constants.ts - 143 + 145 X @@ -6587,11 +6683,11 @@ Record corrections - ORCID src/app/constants.ts - 145 + 147 src/app/constants.ts - 146 + 148 X @@ -6599,7 +6695,7 @@ - ORCID src/app/constants.ts - 150 + 152 X @@ -6607,7 +6703,7 @@ - My ORCID src/app/constants.ts - 151 + 153 X @@ -6615,7 +6711,7 @@ Manage employment dialog src/app/constants.ts - 376 + 378 X @@ -6623,7 +6719,7 @@ Manage education dialog src/app/constants.ts - 378 + 380 X @@ -6631,7 +6727,7 @@ Manage qualification dialog src/app/constants.ts - 380 + 382 X @@ -6639,7 +6735,7 @@ Manage distinction dialog src/app/constants.ts - 382 + 384 X @@ -6647,7 +6743,7 @@ Manage invited position dialog src/app/constants.ts - 384 + 386 X @@ -6655,7 +6751,7 @@ Manage membership dialog src/app/constants.ts - 386 + 388 X @@ -6663,7 +6759,7 @@ Manage service dialog src/app/constants.ts - 388 + 390 X @@ -6671,7 +6767,7 @@ Manage editorial service dialog src/app/constants.ts - 390 + 392 X @@ -6679,7 +6775,7 @@ Manage bibtex dialog src/app/constants.ts - 393 + 395 X @@ -6687,7 +6783,7 @@ Manage external identifier dialog src/app/constants.ts - 395 + 397 X @@ -6695,7 +6791,7 @@ Manage your names dialog src/app/constants.ts - 397 + 399 X @@ -6703,7 +6799,7 @@ Manage your biography dialog src/app/constants.ts - 399 + 401 X @@ -6711,7 +6807,7 @@ Manage your emails dialog src/app/constants.ts - 401 + 403 X @@ -6719,7 +6815,7 @@ Manage your countries dialog src/app/constants.ts - 403 + 405 X @@ -6727,7 +6823,7 @@ Manage your keywords dialog src/app/constants.ts - 405 + 407 X @@ -6735,7 +6831,7 @@ Manage your other IDs dialog src/app/constants.ts - 407 + 409 X @@ -6743,7 +6839,7 @@ Manage your websites & social links dialog src/app/constants.ts - 409 + 411 X @@ -6751,7 +6847,7 @@ Manage funding dialog src/app/constants.ts - 411 + 413 X @@ -6759,7 +6855,7 @@ Manage funding search dialog src/app/constants.ts - 413 + 415 X @@ -6767,7 +6863,7 @@ Manage work dialog src/app/constants.ts - 415 + 417 X @@ -6775,7 +6871,7 @@ Manage work search dialog src/app/constants.ts - 417 + 419 X @@ -6783,7 +6879,7 @@ Manage peer review dialog src/app/constants.ts - 419 + 421 X @@ -19415,6 +19511,266 @@ X + + Your recovery phone number can be used to access your ORCID account if you don't have your authentication app or recovery codes. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 20,23 + + X + + + Your recovery phone number can only be used once. Using it will disable two-factor authentication and reset your 2FA backup options. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 27,30 + + X + + + Your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 37,39 + + X + + + Your current recovery phone number is + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 44 + + X + + + Select a country or location and enter your phone number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 55,57 + + X + + + By entering my phone number and clicking 'Send verification code', I consent to receive an automated one-time verification code from ORCID at the number provided. Message and data rates may apply. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 87,91 + + X + + + Terms of use + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 98 + + X + + + Privacy policy + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 106 + + X + + + Send verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 120,122 + + X + + + Verification code sent to + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 126 + + X + + + Resend in + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 130 + + X + + + seconds + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 134 + + X + + + Verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 150 + + X + + + Enter the 6-digit verification code sent to your device + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.html + 175 + + X + + + Cancel + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 68 + + X + + + Manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 144 + + X + + + Add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 145 + + X + + + Update recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 150 + + X + + + to manage your recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 156 + + X + + + to add a recovery phone number + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 157 + + X + + + We could not send a verification code. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 319 + + X + + + A verification code is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 330 + + X + + + Invalid verification code length + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 335 + + X + + + Phone number is required + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 401 + + X + + + Phone number is too short + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 409 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 433 + + X + + + Phone number is too long + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 412 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 436 + + X + + + Phone number is invalid + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 414 + + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 439 + + X + + + This code was sent to a different number. Send a new code to this number. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 442 + + X + + + Invalid verification code + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 448 + + X + + + That code is no longer valid. Send a new code. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 453 + + X + + + Something went wrong. Please try again. + + src/app/two-factor-recovery-phone/pages/recovery-phone/recovery-phone.component.ts + 465 + + X + Step 1 of 2 - Authentication app