Skip to content

Use query params to check which device needs verif #560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions src/app/auth/components/verify/verify.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<ng-container
*ngIf="
(verifyingPhone || showCaptchaForEmail) && captchaEnabled && !captchaPassed;
(currentVerifyFlow === 'phone' || showCaptchaForEmail) &&
captchaEnabled &&
!captchaPassed;
else verifyFormElement
"
>
Expand All @@ -19,25 +21,27 @@
<ng-template #verifyFormElement>
<form [formGroup]="verifyForm" (ngSubmit)="onSubmit(verifyForm.value)">
<h1 class="page-title">
{{ formTitle }}
{{
currentVerifyFlow === 'email' ? 'Verify Email' : 'Verify Phone Number'
}}
</h1>
<p class="verify-text">
A verification code has been sent to your
{{ verifyingEmail ? 'email address' : 'mobile device' }}.
<span *ngIf="!verifyingEmail"
>Permanent only supports US and Canada numbers.</span
>
{{ currentVerifyFlow === 'email' ? 'email address' : 'mobile device' }}.
<span *ngIf="currentVerifyFlow === 'phone'">
Permanent only supports US and Canada numbers.
</span>
</p>
<p class="verify-text">Enter it below to continue.</p>
<div class="input-container">
<pr-form-input-glam
type="{{ verifyingEmail ? 'email' : 'tel' }}"
type="{{ currentVerifyFlow === 'email' ? 'email' : 'tel' }}"
fieldName="token"
[variant]="'dark'"
placeholder="Code"
[control]="verifyForm.controls['token']"
[config]="{
autocomplete: verifyingEmail ? 'email' : 'tel',
autocomplete: currentVerifyFlow === 'email' ? 'email' : 'tel',
autocorrect: 'off',
autocapitalize: 'off',
spellcheck: 'off'
Expand Down
86 changes: 32 additions & 54 deletions src/app/auth/components/verify/verify.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { environment } from '@root/environments/environment';

import { HttpService } from '@shared/services/http/http.service';
import { ApiService } from '@shared/services/api/api.service';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { EventService } from '@shared/services/event/event.service';

const defaultAuthData = require('@root/test/responses/auth.verify.unverifiedEmail.success.json');
Expand All @@ -32,9 +32,7 @@ describe('VerifyComponent', () => {
const config: TestModuleMetadata = cloneDeep(Testing.BASE_TEST_CONFIG);

config.declarations.push(VerifyComponent);

config.imports.push(SharedModule);

config.providers.push(HttpService);
config.providers.push(ApiService);
config.providers.push(AccountService);
Expand All @@ -48,19 +46,23 @@ describe('VerifyComponent', () => {
},
});

config.providers.push(EventService);
config.providers.push({
provide: Router,
useValue: {
navigate: jasmine.createSpy('navigate'),
navigateByUrl: jasmine.createSpy('navigateByUrl'),
},
});

// Define the re-captcha element as a custom element so it's only mocked out
config.providers.push(EventService);
config.schemas = [CUSTOM_ELEMENTS_SCHEMA];

await TestBed.configureTestingModule(config).compileComponents();

httpMock = TestBed.get(HttpTestingController);

accountService = TestBed.get(AccountService);

const authResponse = new AuthResponse(authResponseData);

accountService.setAccount(authResponse.getAccountVO());
accountService.setArchive(authResponse.getArchiveVO());

Expand All @@ -71,7 +73,6 @@ describe('VerifyComponent', () => {

afterEach(() => {
accountService.clear();
// httpMock.verify();
});

it('should create', async () => {
Expand All @@ -80,59 +81,49 @@ describe('VerifyComponent', () => {
expect(component).toBeTruthy();
});

// it('should send email verification if sendEmail flag set', async () => {
// await init(defaultAuthData, {sendEmail: true});
// const req = httpMock.expectOne(`${environment.apiUrl}/auth/resendMailCreatedAccount`);
// });

// it('should send sms verification if sendSms flag set', async () => {
// await init(defaultAuthData, {sendSms: true});
// const req = httpMock.expectOne(`${environment.apiUrl}/auth/resendTextCreatedAccount`);
// });

it('should require only email verification if only email unverified', async () => {
await init();
await init(defaultAuthData, { sendEmail: true });

expect(component.verifyingEmail).toBeTruthy();
expect(component.needsEmail).toBeTruthy();
expect(component.needsPhone).toBeFalsy();
expect(component.currentVerifyFlow).toBe('email');
expect(component.needsEmail).toBeTrue();
expect(component.needsPhone).toBeFalse();
});

it('should require only phone verification if only phone unverified', async () => {
const unverifiedPhoneData = require('@root/test/responses/auth.verify.unverifiedPhone.success.json');
await init(unverifiedPhoneData);
await init(unverifiedPhoneData, { sendSms: true });

expect(component.verifyingPhone).toBeTruthy();
expect(component.needsPhone).toBeTruthy();
expect(component.needsEmail).toBeFalsy();
expect(component.currentVerifyFlow).toBe('phone');
expect(component.needsPhone).toBeTrue();
expect(component.needsEmail).toBeFalse();
});

it('should require verification of both if both unverified, and verify email first', async () => {
const unverifiedBothData = require('@root/test/responses/auth.verify.unverifiedBoth.success.json');
await init(unverifiedBothData);

expect(component.verifyingEmail).toBeTruthy();
expect(component.needsPhone).toBeTruthy();
expect(component.needsEmail).toBeTruthy();
expect(component.currentVerifyFlow).toBe('email');
expect(component.needsEmail).toBeTrue();
expect(component.needsPhone).toBeTrue();
});

it('should verify email and then switch to phone verification if needed', async () => {
const unverifiedBothData = require('@root/test/responses/auth.verify.unverifiedBoth.success.json');
await init(unverifiedBothData);

expect(component.verifyingEmail).toBeTruthy();
expect(component.needsPhone).toBeTruthy();
expect(component.needsEmail).toBeTruthy();
const account = accountService.getAccount();

expect(account.emailNeedsVerification()).toBeTrue();
expect(account.phoneNeedsVerification()).toBeTrue();

component.onSubmit(component.verifyForm.value).then(() => {
expect(component.waiting).toBeFalsy();
expect(component.verifyingEmail).toBeFalsy();
expect(component.needsEmail).toBeFalsy();
expect(component.needsPhone).toBeTruthy();
expect(component.verifyingPhone).toBeTruthy();
expect(component.waiting).toBeFalse();
expect(component.currentVerifyFlow).toBe('phone');
expect(component.needsEmail).toBeFalse();
expect(component.needsPhone).toBeTrue();
});

expect(component.waiting).toBeTruthy();
expect(component.waiting).toBeTrue();

const verifyEmailResponse = require('@root/test/responses/auth.verify.verifyEmailThenPhone.success.json');
const req = httpMock.expectOne(`${environment.apiUrl}/auth/verify`);
Expand All @@ -141,14 +132,9 @@ describe('VerifyComponent', () => {

it('should verify email and redirect if only email needed', async () => {
const unverifiedEmailData = require('@root/test/responses/auth.verify.unverifiedEmail.success.json');
await init(unverifiedEmailData);
await init(unverifiedEmailData, { sendEmail: true });

const finishSpy = spyOn(component, 'finish');

expect(component.verifyingEmail).toBeTruthy();
expect(component.needsPhone).toBeFalsy();
expect(component.needsEmail).toBeTruthy();

component.onSubmit(component.verifyForm.value).then(() => {
expect(component.waiting).toBeFalsy();
expect(component.needsEmail).toBeFalsy();
Expand All @@ -165,15 +151,9 @@ describe('VerifyComponent', () => {

it('should verify phone and redirect if only phone needed', async () => {
const unverifiedPhoneData = require('@root/test/responses/auth.verify.unverifiedPhone.success.json');
await init(unverifiedPhoneData);
await init(unverifiedPhoneData, { sendSms: true });

const finishSpy = spyOn(component, 'finish');

expect(component.verifyingPhone).toBeTruthy();
expect(component.verifyingEmail).toBeFalsy();
expect(component.needsPhone).toBeTruthy();
expect(component.needsEmail).toBeFalsy();

component.onSubmit(component.verifyForm.value).then(() => {
expect(component.waiting).toBeFalsy();
expect(component.needsEmail).toBeFalsy();
Expand All @@ -190,10 +170,8 @@ describe('VerifyComponent', () => {

it('should show CAPTCHA before verifying phone', async () => {
const unverifiedPhoneData = require('@root/test/responses/auth.verify.unverifiedPhone.success.json');
await init(unverifiedPhoneData);
await init(unverifiedPhoneData, { sendSms: true });

// Testing environments might not have the site key enabled,
// so force captchaEnabled to be true.
component.captchaEnabled = true;

expect(component.captchaPassed).toBeFalsy();
Expand Down
66 changes: 38 additions & 28 deletions src/app/auth/components/verify/verify.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import { SecretsService } from '@shared/services/secrets/secrets.service';
import { RecaptchaErrorParameters } from 'ng-recaptcha';
import { EventService } from '@shared/services/event/event.service';
import { C } from '@angular/cdk/keycodes';

@Component({
selector: 'pr-verify',
Expand All @@ -23,11 +24,9 @@
export class VerifyComponent implements OnInit {
@HostBinding('class.pr-auth-form') classBinding = true;
verifyForm: UntypedFormGroup;
formTitle = 'Verify Email';
waiting: boolean;

verifyingEmail = true;
verifyingPhone = false;
currentVerifyFlow: 'none' | 'email' | 'phone' = 'none';

needsEmail: boolean;
needsPhone: boolean;
Expand Down Expand Up @@ -59,11 +58,21 @@
});
return;
}
const queryParams = route.snapshot.queryParams;

this.needsEmail = account.emailNeedsVerification();
this.needsPhone = account.phoneNeedsVerification();

const queryParams = route.snapshot.queryParams;
if ((this.needsEmail || queryParams.sendEmail) && !queryParams.sendSms) {
this.currentVerifyFlow = 'email';
} else if (
(this.needsPhone || queryParams.sendSms) &&
!queryParams.sendEmail
) {
this.currentVerifyFlow = 'phone';
} else {
this.currentVerifyFlow = 'none';

Check warning on line 74 in src/app/auth/components/verify/verify.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/auth/components/verify/verify.component.ts#L74

Added line #L74 was not covered by tests
}

this.verifyForm = fb.group({
token: [queryParams.token || ''],
Expand Down Expand Up @@ -97,11 +106,7 @@
}
}

if (!this.needsEmail && this.needsPhone) {
this.verifyingEmail = false;
this.verifyingPhone = true;
this.formTitle = 'Verify Phone Number';
} else if (!this.needsEmail) {
if (this.currentVerifyFlow === 'none') {
this.router.navigate(['/private'], { queryParamsHandling: 'preserve' });
}
}
Expand All @@ -119,12 +124,15 @@

let verifyPromise: Promise<AuthResponse>;

if (this.verifyingEmail) {
verifyPromise = this.accountService.verifyEmail(formValue.token);
} else if (this.verifyingPhone) {
verifyPromise = this.accountService.verifyPhone(formValue.token);
} else {
return;
switch (this.currentVerifyFlow) {
case 'email':
verifyPromise = this.accountService.verifyEmail(formValue.token);
break;
case 'phone':
verifyPromise = this.accountService.verifyPhone(formValue.token);
break;
default:
return;

Check warning on line 135 in src/app/auth/components/verify/verify.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/auth/components/verify/verify.component.ts#L135

Added line #L135 was not covered by tests
}

return verifyPromise
Expand All @@ -147,9 +155,7 @@

if (this.needsPhone) {
this.verifyForm.controls['token'].setValue('');
this.verifyingEmail = false;
this.verifyingPhone = true;
this.formTitle = 'Verify Phone Number';
this.currentVerifyFlow = 'phone';
} else {
this.finish();
}
Expand All @@ -167,14 +173,19 @@
this.waiting = true;

let resendPromise: Promise<AuthResponse>;
if (this.verifyingEmail) {
if (this.canSendCodes('email')) {
resendPromise = this.accountService.resendEmailVerification();
}
} else {
if (this.canSendCodes('phone')) {
resendPromise = this.accountService.resendPhoneVerification();
}
switch (this.currentVerifyFlow) {
case 'email':
if (this.canSendCodes('email')) {
resendPromise = this.accountService.resendEmailVerification();

Check warning on line 179 in src/app/auth/components/verify/verify.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/auth/components/verify/verify.component.ts#L179

Added line #L179 was not covered by tests
}
break;

Check warning on line 181 in src/app/auth/components/verify/verify.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/auth/components/verify/verify.component.ts#L181

Added line #L181 was not covered by tests
case 'phone':
if (this.canSendCodes('phone')) {
resendPromise = this.accountService.resendPhoneVerification();
}
break;
default:
return;

Check warning on line 188 in src/app/auth/components/verify/verify.component.ts

View check run for this annotation

Codecov / codecov/patch

src/app/auth/components/verify/verify.component.ts#L188

Added line #L188 was not covered by tests
}

resendPromise
Expand Down Expand Up @@ -203,9 +214,8 @@
.then((response: ArchiveResponse) => {
this.message.showMessage({
message: `${
this.verifyingEmail ? 'Email' : 'Phone number'
this.currentVerifyFlow === 'email' ? 'Email' : 'Phone number'
} verified.`,
style: 'success',
});
if (this.route.snapshot.queryParams.shareByUrl) {
this.router
Expand Down
5 changes: 4 additions & 1 deletion src/app/models/account-vo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ export class AccountVO extends BaseVO {
}

emailNeedsVerification(): boolean {
return this.emailStatus === 'status.auth.unverified';
return (
(this.primaryEmail && !this.emailStatus) ||
this.emailStatus === 'status.auth.unverified'
);
}
}
1 change: 1 addition & 0 deletions src/test/responses/auth.login.success.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"AccountVO": {
"accountId": 1,
"primaryEmail": "[email protected]",
"emailStatus":"type.auth.unverified",
"fullName": "Unit Test",
"address": null,
"address2": null,
Expand Down