From c06cbe2acc97f58be840381d8a383aa984b54672 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 8 Dec 2020 23:17:16 +0200
Subject: [PATCH 01/53] Create service to handle phone verification
---
.../src/core/invitation/invitation-service.ts | 2 +-
.../link-generator/link-generator-service.ts | 9 +++
server/src/core/message/message.ts | 4 ++
server/src/core/phone-verification/index.ts | 0
.../phone-verification-service.ts | 69 +++++++++++++++++++
server/src/core/phone-verification/types.ts | 7 ++
6 files changed, 90 insertions(+), 1 deletion(-)
create mode 100644 server/src/core/phone-verification/index.ts
create mode 100644 server/src/core/phone-verification/phone-verification-service.ts
create mode 100644 server/src/core/phone-verification/types.ts
diff --git a/server/src/core/invitation/invitation-service.ts b/server/src/core/invitation/invitation-service.ts
index 3f05f842..8e3f0877 100644
--- a/server/src/core/invitation/invitation-service.ts
+++ b/server/src/core/invitation/invitation-service.ts
@@ -1,7 +1,7 @@
import { Db, Collection } from 'mongodb';
import { generateId } from '../util';
import { Invitation, DbInvitation, InvitationCreateArgs, InvitationService, InvitationStatus } from './types';
-import { createDbOpFailedError, rethrowIfAppError, createResourceNotFoundError, AppError } from '../error';
+import { createDbOpFailedError, rethrowIfAppError, createResourceNotFoundError } from '../error';
import * as validators from './validator';
import * as messages from '../messages';
diff --git a/server/src/core/link-generator/link-generator-service.ts b/server/src/core/link-generator/link-generator-service.ts
index 1bdb632d..0028a263 100644
--- a/server/src/core/link-generator/link-generator-service.ts
+++ b/server/src/core/link-generator/link-generator-service.ts
@@ -57,4 +57,13 @@ export class Links implements LinkGeneratorService {
return link;
}
+
+ async getPhoneVerificationLink(code: string, shorten: boolean = true): Promise {
+ const link: string = `${this.args.baseUrl}/confirm/phone/${code}`;
+ if (shorten) {
+ return this.args.shortener.shortenLink(link);
+ }
+
+ return link;
+ }
}
\ No newline at end of file
diff --git a/server/src/core/message/message.ts b/server/src/core/message/message.ts
index 575752f5..6bdbb554 100644
--- a/server/src/core/message/message.ts
+++ b/server/src/core/message/message.ts
@@ -51,6 +51,10 @@ export function createMonthlyDistributionReportEmailMessageForOccasionalDonor(do
`;
}
+export function createPhoneVerificationSms(user: User, verificationLink: string): string {
+ return `Hello ${extractFirstName(user.name)}, before you can start making donations, you need to confirm your phone number by clicking ${verificationLink}`
+}
+
function beneficiariesAndAmountReceived(beneficiaries: User[], receivedAmount: number[], type: MessageType): string {
if (type === 'sms') {
return beneficiariesAndAmountReceivedForSms(beneficiaries, receivedAmount);
diff --git a/server/src/core/phone-verification/index.ts b/server/src/core/phone-verification/index.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
new file mode 100644
index 00000000..00d1f2ff
--- /dev/null
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -0,0 +1,69 @@
+import { Db, Collection } from 'mongodb';
+import { generateId } from '../util';
+import { VerificationService } from './types';
+import { UserService, User } from '../user';
+import { SmsProvider } from '../sms';
+import { Links } from '../link-generator';
+import { createPhoneVerificationSms } from '../message';
+import { createDbOpFailedError, rethrowIfAppError } from '../error';
+
+const COLLECTION = 'phone-verifications';
+
+export interface PhoneVerificationRecord {
+ _id: string,
+ phone: string,
+ createdAt: Date,
+ updatedAt: Date,
+}
+
+export interface PhoneVerificationArgs {
+ smsProvider: SmsProvider;
+ users: UserService;
+ links: Links
+}
+
+export class PhoneVerification implements VerificationService {
+ private db: Db;
+ private collection: Collection;
+ private indexesCreated: boolean;
+ private args: PhoneVerificationArgs;
+
+ constructor(db: Db, args: PhoneVerificationArgs) {
+ this.db = db;
+ this.collection = this.db.collection(COLLECTION);
+ this.args = args;
+ this.indexesCreated = false;
+ }
+
+ async createIndexes(): Promise {
+ if (this.indexesCreated) return;
+
+ try {
+ // unique phone index
+ await this.collection.createIndex({ 'phone': 1 }, { unique: true, sparse: false });
+
+ this.indexesCreated = true;
+ }
+ catch (e) {
+ throw createDbOpFailedError(e.message);
+ }
+ }
+
+ async sendSms(user: User): Promise {
+ try {
+ const code = generateId();
+ const link = await this.args.links.getPhoneVerificationLink(code);
+ const smsMessage = createPhoneVerificationSms(user, link);
+ await this.args.smsProvider.sendSms(user.phone, smsMessage);
+ }
+ catch(e) {
+ console.error("Error occured: ", e.message);
+ rethrowIfAppError(e);
+ throw createDbOpFailedError(e.message);
+ }
+ }
+
+ async confirmSms(): Promise {
+ // TODO
+ }
+}
\ No newline at end of file
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
new file mode 100644
index 00000000..fd2ec03c
--- /dev/null
+++ b/server/src/core/phone-verification/types.ts
@@ -0,0 +1,7 @@
+import { User } from '../user';
+
+export interface VerificationService {
+ createIndexes(): Promise;
+ sendSms(user: User): Promise;
+ confirmSms(): Promise;
+}
\ No newline at end of file
From 076c5d8bd8f0dc04d149de978b961e278894801a Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 14 Dec 2020 11:06:07 +0200
Subject: [PATCH 02/53] Rename methods sendSms and confirmSms in module
VerificationService to sendVerificationSms and confirmVerificationSms,
respectiviely
---
.../src/core/phone-verification/phone-verification-service.ts | 4 ++--
server/src/core/phone-verification/types.ts | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 00d1f2ff..62943373 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -49,7 +49,7 @@ export class PhoneVerification implements VerificationService {
}
}
- async sendSms(user: User): Promise {
+ async sendVerificationSms(user: User): Promise {
try {
const code = generateId();
const link = await this.args.links.getPhoneVerificationLink(code);
@@ -63,7 +63,7 @@ export class PhoneVerification implements VerificationService {
}
}
- async confirmSms(): Promise {
+ async confirmVerificationSms(): Promise {
// TODO
}
}
\ No newline at end of file
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
index fd2ec03c..4e7effdf 100644
--- a/server/src/core/phone-verification/types.ts
+++ b/server/src/core/phone-verification/types.ts
@@ -2,6 +2,6 @@ import { User } from '../user';
export interface VerificationService {
createIndexes(): Promise;
- sendSms(user: User): Promise;
- confirmSms(): Promise;
+ sendVerificationSms(user: User): Promise;
+ confirmVerificationSms(): Promise;
}
\ No newline at end of file
From 4b126d204b1b282e8ac6f2fbd9c9bcea851d303c Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 14 Dec 2020 11:58:02 +0200
Subject: [PATCH 03/53] Rename method confirmVerficationSms to
confirmVerificationCode
---
server/src/core/error.ts | 5 +++++
.../phone-verification-service.ts | 22 ++++++++++++++++---
server/src/core/phone-verification/types.ts | 2 +-
3 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/server/src/core/error.ts b/server/src/core/error.ts
index ba386075..b7938517 100644
--- a/server/src/core/error.ts
+++ b/server/src/core/error.ts
@@ -49,6 +49,7 @@ export type ErrorCode =
| 'loginFailed'
| 'invalidToken'
| 'resourceNotFound'
+ | 'phoneVerificationRecordNotFound'
| 'uniquenessFailed'
| 'paymentRequestFailed'
| 'b2cRequestFailed'
@@ -178,4 +179,8 @@ export function createTransactionRejectedError(message: string = messages.ERROR_
export function createInsufficientFundsError(message: string) {
return createAppError(message, 'insufficientFunds');
+}
+
+export function createPhoneVerificationRecordNotFound(message: string) {
+ return createAppError(message, 'phoneVerificationRecordNotFound');
}
\ No newline at end of file
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 62943373..01cee0c6 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -5,7 +5,8 @@ import { UserService, User } from '../user';
import { SmsProvider } from '../sms';
import { Links } from '../link-generator';
import { createPhoneVerificationSms } from '../message';
-import { createDbOpFailedError, rethrowIfAppError } from '../error';
+import { createDbOpFailedError, rethrowIfAppError, createPhoneVerificationRecordNotFound } from '../error';
+import * as messages from '../messages';
const COLLECTION = 'phone-verifications';
@@ -63,7 +64,22 @@ export class PhoneVerification implements VerificationService {
}
}
- async confirmVerificationSms(): Promise {
- // TODO
+ async confirmVerificationCode(code: string): Promise {
+ try {
+ const record = await this.collection.findOne({
+ _id: code
+ });
+ if (!record) {
+ throw createPhoneVerificationRecordNotFound(messages.ERROR_INVITATION_NOT_FOUND);
+ }
+ else {
+
+ }
+ }
+ catch(e) {
+ console.error("Error occured: ", e.message);
+ rethrowIfAppError(e);
+ throw createDbOpFailedError(e.message);
+ }
}
}
\ No newline at end of file
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
index 4e7effdf..36e24397 100644
--- a/server/src/core/phone-verification/types.ts
+++ b/server/src/core/phone-verification/types.ts
@@ -3,5 +3,5 @@ import { User } from '../user';
export interface VerificationService {
createIndexes(): Promise;
sendVerificationSms(user: User): Promise;
- confirmVerificationSms(): Promise;
+ confirmVerificationCode(): Promise;
}
\ No newline at end of file
From 62a9341c7376c8c63292d5b068bec8494833b11b Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 14 Dec 2020 12:02:00 +0200
Subject: [PATCH 04/53] Create error message for when there's no record
corresponding to a specific verification code
---
server/src/core/messages.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/server/src/core/messages.ts b/server/src/core/messages.ts
index 457d757a..e6fcce02 100644
--- a/server/src/core/messages.ts
+++ b/server/src/core/messages.ts
@@ -28,4 +28,5 @@ export const ERROR_REFUND_REQUEST_REJECTED = 'Refund request rejected';
export const ERROR_USER_BLOCKED_FROM_TRANSACTIONS = 'User is blocked from making transactions at the moment.';
export const ERROR_NO_BALANCE_FOR_REFUNDS = 'No available balance to request for refund.';
export const ERROR_TRANSACTION_REJECTED = 'Transaction rejected';
+export const ERROR_PHONE_VERIFICATION_RECORD_NOT_FOUND = 'Phone verification record not found';
From e31f4d3b8fe51f86d35d1707fd757eae20955b4a Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 14 Dec 2020 15:57:16 +0200
Subject: [PATCH 05/53] Implement user service method for verifying donor by
setting isPhoneVerified field to true
---
server/src/core/error.ts | 7 ++++++-
server/src/core/messages.ts | 1 +
.../phone-verification-service.ts | 14 ++++++++------
server/src/core/phone-verification/types.ts | 2 +-
server/src/core/user/types.ts | 5 +++++
server/src/core/user/user-service.ts | 19 +++++++++++++++++++
6 files changed, 40 insertions(+), 8 deletions(-)
diff --git a/server/src/core/error.ts b/server/src/core/error.ts
index b7938517..1b3ab0b1 100644
--- a/server/src/core/error.ts
+++ b/server/src/core/error.ts
@@ -69,6 +69,7 @@ export type ErrorCode =
| 'messageDeliveryFailed'
| 'emailDeliveryFailed'
| 'linkShorteningFailed'
+ | 'phoneAlreadyVerified'
/**
* This error should only be thrown when a transaction fails
* because the user's transactions are blocked (based on the transactionsBlockedReason field)
@@ -181,6 +182,10 @@ export function createInsufficientFundsError(message: string) {
return createAppError(message, 'insufficientFunds');
}
-export function createPhoneVerificationRecordNotFound(message: string) {
+export function createPhoneVerificationRecordNotFoundError(message: string) {
return createAppError(message, 'phoneVerificationRecordNotFound');
+}
+
+export function createPhoneAlreadyVerifiedError(message: string) {
+ return createAppError(message, 'phoneAlreadyVerified');
}
\ No newline at end of file
diff --git a/server/src/core/messages.ts b/server/src/core/messages.ts
index e6fcce02..3a5c2766 100644
--- a/server/src/core/messages.ts
+++ b/server/src/core/messages.ts
@@ -29,4 +29,5 @@ export const ERROR_USER_BLOCKED_FROM_TRANSACTIONS = 'User is blocked from making
export const ERROR_NO_BALANCE_FOR_REFUNDS = 'No available balance to request for refund.';
export const ERROR_TRANSACTION_REJECTED = 'Transaction rejected';
export const ERROR_PHONE_VERIFICATION_RECORD_NOT_FOUND = 'Phone verification record not found';
+export const ERROR_PHONE_ALREADY_VERIFIED = 'Phone already verified'
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 01cee0c6..e4a184cf 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -5,7 +5,7 @@ import { UserService, User } from '../user';
import { SmsProvider } from '../sms';
import { Links } from '../link-generator';
import { createPhoneVerificationSms } from '../message';
-import { createDbOpFailedError, rethrowIfAppError, createPhoneVerificationRecordNotFound } from '../error';
+import { createDbOpFailedError, rethrowIfAppError, createPhoneVerificationRecordNotFoundError, createPhoneAlreadyVerifiedError } from '../error';
import * as messages from '../messages';
const COLLECTION = 'phone-verifications';
@@ -13,6 +13,7 @@ const COLLECTION = 'phone-verifications';
export interface PhoneVerificationRecord {
_id: string,
phone: string,
+ isVerified: boolean,
createdAt: Date,
updatedAt: Date,
}
@@ -66,14 +67,15 @@ export class PhoneVerification implements VerificationService {
async confirmVerificationCode(code: string): Promise {
try {
- const record = await this.collection.findOne({
- _id: code
- });
+ const record = await this.collection.findOne({ _id: code });
if (!record) {
- throw createPhoneVerificationRecordNotFound(messages.ERROR_INVITATION_NOT_FOUND);
+ throw createPhoneVerificationRecordNotFoundError(messages.ERROR_PHONE_VERIFICATION_RECORD_NOT_FOUND);
+ }
+ else if (record.isVerified) {
+ throw createPhoneAlreadyVerifiedError(messages.ERROR_PHONE_ALREADY_VERIFIED);
}
else {
-
+ const user = await this.args.users.getByPhone(record.phone);
}
}
catch(e) {
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
index 36e24397..35b4fcec 100644
--- a/server/src/core/phone-verification/types.ts
+++ b/server/src/core/phone-verification/types.ts
@@ -3,5 +3,5 @@ import { User } from '../user';
export interface VerificationService {
createIndexes(): Promise;
sendVerificationSms(user: User): Promise;
- confirmVerificationCode(): Promise;
+ confirmVerificationCode(code: string): Promise;
}
\ No newline at end of file
diff --git a/server/src/core/user/types.ts b/server/src/core/user/types.ts
index 5150face..129106af 100644
--- a/server/src/core/user/types.ts
+++ b/server/src/core/user/types.ts
@@ -15,6 +15,11 @@ export interface User {
email?: string,
name: string,
isAnonymous?: boolean,
+ /**
+ * indicates whether or not the phone
+ * of the corresponding donor has been verified.
+ */
+ isPhoneVerified?: boolean,
/**
* indicates whether or not the added beneficiary user
* has been approved to receive funds from any donor.
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index f597409a..4bb93770 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -29,6 +29,7 @@ const SAFE_USER_PROJECTION = {
phone: 1,
email: 1,
name: 1,
+ isPhoneVerified: 1,
isVetted: 1,
beneficiaryStatus: 1,
addedBy: 1,
@@ -43,6 +44,7 @@ const SAFE_USER_PROJECTION = {
const NOMINATED_USER_PROJECTION = { _id: 1, phone: 1, name: 1, createdAt: 1 };
const ALL_DONORS_PROJECTION = { _id: 1, phone: 1, name: 1, email: 1, createdAt: 1 };
const RELATED_BENEFICIARY_PROJECTION = { _id: 1, name: 1, addedBy: 1, createdAt: 1};
+const VERIFIED_DONOR_PROTECTION = { _id: 1, phone: 1, name: 1, isPhoneVerified: 1, createdAt: 1, updatedAt: 1 };
/**
* removes fields that should
@@ -868,4 +870,21 @@ export class Users implements UserService {
throw createDbOpFailedError(e.message);
}
}
+
+ async verifyDonor(donor: User): Promise {
+ try {
+ const verifiedDonor = await this.collection.findOneAndUpdate(
+ { _id: donor._id },
+ {
+ $set: { isPhoneVerified: true },
+ },
+ { upsert: true, returnOriginal: false, projection: VERIFIED_DONOR_PROTECTION }
+ );
+
+ return verifiedDonor.value;
+ }
+ catch(e) {
+
+ }
+ }
}
From 9d03e4cbb09fc400c6d6b72b20fcbb9c7d4ea024 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 14 Dec 2020 15:58:14 +0200
Subject: [PATCH 06/53] Implement user service method for verifying donor by
setting isPhoneVerified field to true
---
server/src/core/user/user-service.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index 4bb93770..2f29443c 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -884,7 +884,7 @@ export class Users implements UserService {
return verifiedDonor.value;
}
catch(e) {
-
+ throw createDbOpFailedError(e.message);
}
}
}
From 83c3ae6e464b6fdc6afe522be34dadc02d48de76 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 14 Dec 2020 16:16:46 +0200
Subject: [PATCH 07/53] Update sms format for phone verification
---
server/src/core/message/message.ts | 2 +-
.../core/phone-verification/phone-verification-service.ts | 1 +
server/src/core/user/types.ts | 7 ++++++-
server/src/core/user/user-service.ts | 8 ++++----
4 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/server/src/core/message/message.ts b/server/src/core/message/message.ts
index 6bdbb554..4489bde6 100644
--- a/server/src/core/message/message.ts
+++ b/server/src/core/message/message.ts
@@ -52,7 +52,7 @@ export function createMonthlyDistributionReportEmailMessageForOccasionalDonor(do
}
export function createPhoneVerificationSms(user: User, verificationLink: string): string {
- return `Hello ${extractFirstName(user.name)}, before you can start making donations, you need to confirm your phone number by clicking ${verificationLink}`
+ return `Hello ${extractFirstName(user.name)}, you need to confirm your phone number by clicking ${verificationLink}`
}
function beneficiariesAndAmountReceived(beneficiaries: User[], receivedAmount: number[], type: MessageType): string {
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index e4a184cf..73a9c59d 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -76,6 +76,7 @@ export class PhoneVerification implements VerificationService {
}
else {
const user = await this.args.users.getByPhone(record.phone);
+ await this.args.users.verifyUser(user);
}
}
catch(e) {
diff --git a/server/src/core/user/types.ts b/server/src/core/user/types.ts
index 129106af..52c0e4af 100644
--- a/server/src/core/user/types.ts
+++ b/server/src/core/user/types.ts
@@ -302,7 +302,12 @@ export interface UserService {
/**
* Returns all users with the role donor
*/
- getAllDonors(): Promise
+ getAllDonors(): Promise;
+ /**
+ * Sets the isPhoneVerified field in user
+ * to true
+ */
+ verifyUser(user: User): Promise
};
export interface UserCreateAnonymousArgs {
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index 2f29443c..3078873f 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -871,17 +871,17 @@ export class Users implements UserService {
}
}
- async verifyDonor(donor: User): Promise {
+ public async verifyUser(user: User): Promise {
try {
- const verifiedDonor = await this.collection.findOneAndUpdate(
- { _id: donor._id },
+ const verifiedUser = await this.collection.findOneAndUpdate(
+ { _id: user._id },
{
$set: { isPhoneVerified: true },
},
{ upsert: true, returnOriginal: false, projection: VERIFIED_DONOR_PROTECTION }
);
- return verifiedDonor.value;
+ return verifiedUser.value;
}
catch(e) {
throw createDbOpFailedError(e.message);
From 94e579b734695c72c6e295bda43d275c3cf45919 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 15 Dec 2020 10:52:11 +0200
Subject: [PATCH 08/53] Commit master branch changes
---
server/src/core/user/types.ts | 8 --------
1 file changed, 8 deletions(-)
diff --git a/server/src/core/user/types.ts b/server/src/core/user/types.ts
index 440a9746..52c0e4af 100644
--- a/server/src/core/user/types.ts
+++ b/server/src/core/user/types.ts
@@ -302,20 +302,12 @@ export interface UserService {
/**
* Returns all users with the role donor
*/
-<<<<<<< HEAD
getAllDonors(): Promise;
/**
* Sets the isPhoneVerified field in user
* to true
*/
verifyUser(user: User): Promise
-=======
- getAllDonors(): Promise
- /**
- * Returns all beneficiaries
- */
- getAllBeneficiaries(): Promise
->>>>>>> master
};
export interface UserCreateAnonymousArgs {
From 2605035acf6b3e436fb0fd80329e208ab708b79a Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 15 Dec 2020 15:22:28 +0200
Subject: [PATCH 09/53] Create event emitters and listeners for when a user is
created or activated
---
server/src/core/app.ts | 2 ++
server/src/core/bootstrap.ts | 11 +++++++
server/src/core/event/event-bus.ts | 18 +++++++++-
server/src/core/event/event-name.ts | 5 +--
server/src/core/phone-verification/index.ts | 2 ++
.../phone-verification-service.ts | 33 ++++++++++++++++++-
server/src/core/user/index.ts | 2 +-
server/src/core/user/types.ts | 8 ++++-
server/src/core/user/user-service.ts | 6 ++++
server/src/rest/index.ts | 3 +-
server/src/rest/middleware.ts | 1 +
server/src/rest/routes/verifications.ts | 7 ++++
12 files changed, 91 insertions(+), 7 deletions(-)
create mode 100644 server/src/rest/routes/verifications.ts
diff --git a/server/src/core/app.ts b/server/src/core/app.ts
index a610429b..6e5b1fe2 100644
--- a/server/src/core/app.ts
+++ b/server/src/core/app.ts
@@ -5,6 +5,7 @@ import { DonationDistributionService } from './distribution';
import { StatsService } from './stat';
import { DistributionReportService } from './distribution-report';
import { BulkMessageService } from './bulk-messaging';
+import { VerificationService } from './phone-verification';
export interface App {
users: UserService;
@@ -13,6 +14,7 @@ export interface App {
donationDistributions: DonationDistributionService;
stats: StatsService;
distributionReports: DistributionReportService;
+ phoneVerification: VerificationService;
bulkMessages: BulkMessageService;
};
diff --git a/server/src/core/bootstrap.ts b/server/src/core/bootstrap.ts
index 51cb6fcf..82fbd9e6 100644
--- a/server/src/core/bootstrap.ts
+++ b/server/src/core/bootstrap.ts
@@ -14,6 +14,7 @@ import { Statistics } from './stat';
import { DistributionReports } from './distribution-report';
import { Links, BitlyLinkShortener } from './link-generator';
import { BulkMessages, DefaultMessageContextFactory } from './bulk-messaging';
+import { PhoneVerification } from './phone-verification';
export async function bootstrap(config: AppConfig): Promise {
const client = await getDbConnection(config.dbUri);
@@ -66,6 +67,7 @@ export async function bootstrap(config: AppConfig): Promise {
apiKey: config.atApiKey,
sender: config.atSmsSender
});
+
const emailProvider = new SendGridEmailProvider({
apiKey: config.sendgridApiKey,
emailSender: config.emailSender
@@ -94,6 +96,13 @@ export async function bootstrap(config: AppConfig): Promise {
links
});
+ const phoneVerification = new PhoneVerification(db, {
+ smsProvider,
+ users,
+ links,
+ eventBus
+ });
+
const messageContextFactory = new DefaultMessageContextFactory({
baseUrl: config.webappBaseUrl,
linkGenerator: links
@@ -108,12 +117,14 @@ export async function bootstrap(config: AppConfig): Promise {
await users.createIndexes();
await transactions.createIndexes();
await invitations.createIndexes();
+ await phoneVerification.createIndexes();
return {
users,
transactions,
invitations,
donationDistributions,
+ phoneVerification,
stats,
distributionReports,
bulkMessages
diff --git a/server/src/core/event/event-bus.ts b/server/src/core/event/event-bus.ts
index 2e51d840..14f54673 100644
--- a/server/src/core/event/event-bus.ts
+++ b/server/src/core/event/event-bus.ts
@@ -1,5 +1,5 @@
import { EventEmitter } from 'events';
-import { UserInvitationEventData } from '../user';
+import { UserCreatedEventData, UserActivatedEventData, UserInvitationEventData } from '../user';
import { TransactionCompletedEventData } from '../payment';
import * as EventName from './event-name';
@@ -32,10 +32,26 @@ export class EventBus extends EventEmitter {
this.on(EventName.USER_INVITATION_CREATED, listener);
}
+ onUserCreated(listener: Listener): void {
+ this.on(EventName.USER_CREATED, listener);
+ }
+
+ onUserActivated(listener: Listener): void {
+ this.on(EventName.USER_ACTIVATED, listener);
+ }
+
emitTransactionCompleted(eventData: TransactionCompletedEventData): void {
this.innerEmit(EventName.TRANSACTION_COMPLETED, eventData);
}
+ emitUserCreated(eventData: UserCreatedEventData): void {
+ this.innerEmit(EventName.USER_CREATED, eventData);
+ }
+
+ emitUserActivated(eventData: UserActivatedEventData): void {
+ this.innerEmit(EventName.USER_ACTIVATED, eventData);
+ }
+
onTransactionCompleted(listener: Listener): void {
this.on(EventName.TRANSACTION_COMPLETED, listener);
}
diff --git a/server/src/core/event/event-name.ts b/server/src/core/event/event-name.ts
index 20a2ba5a..92e99708 100644
--- a/server/src/core/event/event-name.ts
+++ b/server/src/core/event/event-name.ts
@@ -1,5 +1,6 @@
const USER_INVITATION_CREATED = 'userInvitationCreated';
const TRANSACTION_COMPLETED = 'transactionCompleted';
+const USER_CREATED = 'userCreated';
+const USER_ACTIVATED = 'userActivated';
-export { USER_INVITATION_CREATED };
-export { TRANSACTION_COMPLETED };
+export { USER_INVITATION_CREATED, TRANSACTION_COMPLETED, USER_CREATED, USER_ACTIVATED };
diff --git a/server/src/core/phone-verification/index.ts b/server/src/core/phone-verification/index.ts
index e69de29b..a6d96bb9 100644
--- a/server/src/core/phone-verification/index.ts
+++ b/server/src/core/phone-verification/index.ts
@@ -0,0 +1,2 @@
+export { VerificationService } from './types';
+export * from './phone-verification-service';
\ No newline at end of file
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 73a9c59d..ce7a549a 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -7,6 +7,8 @@ import { Links } from '../link-generator';
import { createPhoneVerificationSms } from '../message';
import { createDbOpFailedError, rethrowIfAppError, createPhoneVerificationRecordNotFoundError, createPhoneAlreadyVerifiedError } from '../error';
import * as messages from '../messages';
+import { EventBus, Event } from '../event';
+import { UserCreatedEventData, UserActivatedEventData } from '../user';
const COLLECTION = 'phone-verifications';
@@ -21,7 +23,8 @@ export interface PhoneVerificationRecord {
export interface PhoneVerificationArgs {
smsProvider: SmsProvider;
users: UserService;
- links: Links
+ links: Links;
+ eventBus: EventBus;
}
export class PhoneVerification implements VerificationService {
@@ -35,6 +38,34 @@ export class PhoneVerification implements VerificationService {
this.collection = this.db.collection(COLLECTION);
this.args = args;
this.indexesCreated = false;
+
+ this.registerEventHandlers();
+ }
+
+ private registerEventHandlers() {
+ this.args.eventBus.onUserCreated(event => this.handleUserCreated(event));
+ this.args.eventBus.onUserActivated(event => this.handleUserActivated(event));
+ }
+
+ async handleUserCreated(event: Event) {
+ console.log('Calling handleUserCreated...');
+ return await this.handleUserCreatedOrActivated(event);
+ }
+
+ async handleUserActivated(event: Event) {
+ console.log('Calling handleUserActivated...');
+ return await this.handleUserCreatedOrActivated(event);
+ }
+
+ async handleUserCreatedOrActivated(event: Event) {
+ const { data: { user } } = event;
+
+ try {
+ await this.sendVerificationSms(user);
+ }
+ catch(e) {
+ console.error('Error occurred when handling event', event, e);
+ }
}
async createIndexes(): Promise {
diff --git a/server/src/core/user/index.ts b/server/src/core/user/index.ts
index 583e90e1..48e0ea19 100644
--- a/server/src/core/user/index.ts
+++ b/server/src/core/user/index.ts
@@ -1,2 +1,2 @@
-export { User, UserCreateArgs, UserService, UserRole, UserInvitationEventData, UserAddVettedBeneficiaryArgs } from './types';
+export { User, UserCreateArgs, UserService, UserRole, UserInvitationEventData, UserAddVettedBeneficiaryArgs, UserCreatedEventData, UserActivatedEventData } from './types';
export { Users, COLLECTION } from './user-service';
\ No newline at end of file
diff --git a/server/src/core/user/types.ts b/server/src/core/user/types.ts
index 52c0e4af..19bc9c4b 100644
--- a/server/src/core/user/types.ts
+++ b/server/src/core/user/types.ts
@@ -319,4 +319,10 @@ export interface UserCreateAnonymousArgs {
export interface UserDonateAnonymouslyArgs extends UserCreateAnonymousArgs {
amount: number
-}
\ No newline at end of file
+}
+
+export interface UserCreatedEventData {
+ user: User;
+}
+
+export interface UserActivatedEventData extends UserCreatedEventData {}
\ No newline at end of file
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index 3078873f..17fed0ee 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -139,6 +139,7 @@ export class Users implements UserService {
_id: generateId(),
phone: args.phone,
name: args.name,
+ isPhoneVerified: false,
addedBy: '',
donors: [],
roles: ['donor'],
@@ -162,6 +163,7 @@ export class Users implements UserService {
}
const res = await this.collection.insertOne(user);
+ this.eventBus.emitUserCreated({ user: getSafeUser(res.ops[0]) });
return getSafeUser(res.ops[0]);
}
catch (e) {
@@ -274,6 +276,7 @@ export class Users implements UserService {
_id: generateId(),
password: '',
phone,
+ isPhoneVerified: false,
name,
addedBy: nominatorId,
createdAt: new Date(),
@@ -288,6 +291,8 @@ export class Users implements UserService {
},
{ upsert: true, returnOriginal: false, projection: NOMINATED_USER_PROJECTION }
);
+
+ this.eventBus.emitUserActivated({ user: getSafeUser(result.value) });
return getSafeUser(result.value);
}
catch (e) {
@@ -331,6 +336,7 @@ export class Users implements UserService {
},
{ upsert: true, returnOriginal: false, projection: NOMINATED_USER_PROJECTION }
);
+ this.eventBus.emitUserActivated({ user: getSafeUser(result.value) });
return getSafeUser(result.value);
}
catch (e) {
diff --git a/server/src/rest/index.ts b/server/src/rest/index.ts
index d7d30ec9..fc88061c 100644
--- a/server/src/rest/index.ts
+++ b/server/src/rest/index.ts
@@ -1,7 +1,7 @@
import { Express, Router } from 'express';
import { messages } from '../core';
import { errorHandler, error404Handler } from './middleware';
-import { root, users, donations, transactions, invitations, refunds, stats } from './routes';
+import { root, users, donations, transactions, invitations, verifications, refunds, stats } from './routes';
export function mountRestApi(server: Express, apiRoot: string) {
const router = Router();
@@ -12,6 +12,7 @@ export function mountRestApi(server: Express, apiRoot: string) {
router.use('/invitations', invitations);
router.use('/refunds', refunds);
router.use('/stats', stats);
+ router.use('/verifications', verifications);
router.use('/', root);
router.use(errorHandler());
diff --git a/server/src/rest/middleware.ts b/server/src/rest/middleware.ts
index a377618c..6e49c714 100644
--- a/server/src/rest/middleware.ts
+++ b/server/src/rest/middleware.ts
@@ -28,6 +28,7 @@ export const errorHandler = (): ErrorRequestHandler =>
case 'loginFailed':
return sendErrorResponse(res, statusCodes.STATUS_UNAUTHORIZED, error);
case 'resourceNotFound':
+ case 'phoneVerificationRecordNotFound':
return sendErrorResponse(res, statusCodes.STATUS_NOT_FOUND, error);
case 'uniquenessFailed':
return sendErrorResponse(res, statusCodes.STATUS_CONFLICT, error);
diff --git a/server/src/rest/routes/verifications.ts b/server/src/rest/routes/verifications.ts
new file mode 100644
index 00000000..1dbf7ac1
--- /dev/null
+++ b/server/src/rest/routes/verifications.ts
@@ -0,0 +1,7 @@
+import { Router } from 'express';
+import { wrapResponse } from '../middleware';
+
+export const verifications = Router();
+
+verifications.put('/phone/:id', wrapResponse(
+ req => req.core.phoneVerification.confirmVerificationCode(req.params.id)));
\ No newline at end of file
From 319f28689fd589bdf939adc45f755da300f22a86 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 15 Dec 2020 15:35:34 +0200
Subject: [PATCH 10/53] implement error handling cases for error codes
phoneAlreadyVerfied and phoneVerificationRecordNotFound
---
server/src/rest/middleware.ts | 1 +
server/src/rest/routes/index.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/server/src/rest/middleware.ts b/server/src/rest/middleware.ts
index 6e49c714..bdbc0c87 100644
--- a/server/src/rest/middleware.ts
+++ b/server/src/rest/middleware.ts
@@ -31,6 +31,7 @@ export const errorHandler = (): ErrorRequestHandler =>
case 'phoneVerificationRecordNotFound':
return sendErrorResponse(res, statusCodes.STATUS_NOT_FOUND, error);
case 'uniquenessFailed':
+ case 'phoneAlreadyVerified':
return sendErrorResponse(res, statusCodes.STATUS_CONFLICT, error);
case 'paymentRequestFailed':
case 'activationFailed':
diff --git a/server/src/rest/routes/index.ts b/server/src/rest/routes/index.ts
index 8f6208da..04fe7b93 100644
--- a/server/src/rest/routes/index.ts
+++ b/server/src/rest/routes/index.ts
@@ -5,3 +5,4 @@ export { transactions } from './transactions';
export { invitations } from './invitations';
export { refunds } from './refunds';
export { stats } from './stats';
+export { verifications } from './verifications';
From 4cb51bf80e54900da703938a3245e3ff567f1834 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 15 Dec 2020 16:03:05 +0200
Subject: [PATCH 11/53] Resolve error 'Property getAllBeneficiaries does not
exist'
---
server/src/core/user/types.ts | 6 +++++-
server/src/core/user/user-service.ts | 10 ++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/server/src/core/user/types.ts b/server/src/core/user/types.ts
index 19bc9c4b..471a1a03 100644
--- a/server/src/core/user/types.ts
+++ b/server/src/core/user/types.ts
@@ -307,7 +307,11 @@ export interface UserService {
* Sets the isPhoneVerified field in user
* to true
*/
- verifyUser(user: User): Promise
+ verifyUser(user: User): Promise;
+ /**
+ * Returns all beneficiaries
+ */
+ getAllBeneficiaries(): Promise
};
export interface UserCreateAnonymousArgs {
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index 17fed0ee..02adfc29 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -893,4 +893,14 @@ export class Users implements UserService {
throw createDbOpFailedError(e.message);
}
}
+
+ async getAllBeneficiaries(): Promise {
+ try {
+ const donors = await this.collection.find({ roles: { $in: ['beneficiary'] } }, { projection: SAFE_USER_PROJECTION }).toArray();
+ return donors;
+ }
+ catch (e) {
+ throw createDbOpFailedError(e.message);
+ }
+ }
}
From 6fc807502f08ff97bd56b06a2617291087f44f63 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Wed, 16 Dec 2020 17:41:25 +0200
Subject: [PATCH 12/53] Return corresponding verification record after
verifying phone number
---
server/src/core/event/event-bus.ts | 2 ++
.../link-generator/link-generator-service.ts | 4 ++--
.../phone-verification-service.ts | 22 ++++++++++++++-----
server/src/core/phone-verification/types.ts | 9 +++++++-
4 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/server/src/core/event/event-bus.ts b/server/src/core/event/event-bus.ts
index 14f54673..813b2af0 100644
--- a/server/src/core/event/event-bus.ts
+++ b/server/src/core/event/event-bus.ts
@@ -33,6 +33,7 @@ export class EventBus extends EventEmitter {
}
onUserCreated(listener: Listener): void {
+ console.log('In onUserCreated...');
this.on(EventName.USER_CREATED, listener);
}
@@ -45,6 +46,7 @@ export class EventBus extends EventEmitter {
}
emitUserCreated(eventData: UserCreatedEventData): void {
+ console.log('In emitUserCreated...');
this.innerEmit(EventName.USER_CREATED, eventData);
}
diff --git a/server/src/core/link-generator/link-generator-service.ts b/server/src/core/link-generator/link-generator-service.ts
index 0028a263..7d954bf6 100644
--- a/server/src/core/link-generator/link-generator-service.ts
+++ b/server/src/core/link-generator/link-generator-service.ts
@@ -59,11 +59,11 @@ export class Links implements LinkGeneratorService {
}
async getPhoneVerificationLink(code: string, shorten: boolean = true): Promise {
- const link: string = `${this.args.baseUrl}/confirm/phone/${code}`;
+ const link: string = `${this.args.baseUrl}/verifications/phone/${code}`;
if (shorten) {
return this.args.shortener.shortenLink(link);
}
-
+
return link;
}
}
\ No newline at end of file
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index ce7a549a..860f720c 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -1,6 +1,6 @@
-import { Db, Collection } from 'mongodb';
+import { Db, Collection, FindAndModifyWriteOpResultObject } from 'mongodb';
import { generateId } from '../util';
-import { VerificationService } from './types';
+import { VerificationRecord, VerificationService } from './types';
import { UserService, User } from '../user';
import { SmsProvider } from '../sms';
import { Links } from '../link-generator';
@@ -12,7 +12,7 @@ import { UserCreatedEventData, UserActivatedEventData } from '../user';
const COLLECTION = 'phone-verifications';
-export interface PhoneVerificationRecord {
+export interface PhoneVerificationRecord extends VerificationRecord {
_id: string,
phone: string,
isVerified: boolean,
@@ -86,6 +86,7 @@ export class PhoneVerification implements VerificationService {
try {
const code = generateId();
const link = await this.args.links.getPhoneVerificationLink(code);
+ console.log('generated link: ', link);
const smsMessage = createPhoneVerificationSms(user, link);
await this.args.smsProvider.sendSms(user.phone, smsMessage);
}
@@ -96,7 +97,7 @@ export class PhoneVerification implements VerificationService {
}
}
- async confirmVerificationCode(code: string): Promise {
+ async confirmVerificationCode(code: string): Promise {
try {
const record = await this.collection.findOne({ _id: code });
if (!record) {
@@ -106,8 +107,19 @@ export class PhoneVerification implements VerificationService {
throw createPhoneAlreadyVerifiedError(messages.ERROR_PHONE_ALREADY_VERIFIED);
}
else {
- const user = await this.args.users.getByPhone(record.phone);
+ const result = await this.collection.findOneAndUpdate(
+ { _id: code },
+ {
+ $set: { isVerified: true },
+ $currentDate: { updatedAt: true },
+ },
+ { upsert: true, returnOriginal: false }
+ );
+
+ const user = await this.args.users.getByPhone(result.value.phone);
await this.args.users.verifyUser(user);
+ return result.value;
+
}
}
catch(e) {
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
index 35b4fcec..f55824af 100644
--- a/server/src/core/phone-verification/types.ts
+++ b/server/src/core/phone-verification/types.ts
@@ -1,7 +1,14 @@
import { User } from '../user';
+export interface VerificationRecord {
+ _id: string,
+ isVerified: boolean,
+ createdAt: Date,
+ updatedAt: Date,
+}
+
export interface VerificationService {
createIndexes(): Promise;
sendVerificationSms(user: User): Promise;
- confirmVerificationCode(code: string): Promise;
+ confirmVerificationCode(code: string): Promise;
}
\ No newline at end of file
From e8d91130c804f5081d99d3ec7cf3214924a898ea Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Wed, 16 Dec 2020 17:45:21 +0200
Subject: [PATCH 13/53] Design and implement phone verification page
---
webapp/src/services/index.ts | 1 +
webapp/src/services/verifications.ts | 9 +++++
webapp/src/store/actions.ts | 7 +++-
webapp/src/store/index.ts | 1 +
webapp/src/store/mutations.ts | 6 ++++
webapp/src/types.ts | 9 +++++
webapp/src/views/verify-phone.vue | 50 ++++++++++++++++++++++++++++
7 files changed, 82 insertions(+), 1 deletion(-)
create mode 100644 webapp/src/services/verifications.ts
create mode 100644 webapp/src/views/verify-phone.vue
diff --git a/webapp/src/services/index.ts b/webapp/src/services/index.ts
index a8651070..e23d3ff9 100644
--- a/webapp/src/services/index.ts
+++ b/webapp/src/services/index.ts
@@ -4,5 +4,6 @@ export { Refunds } from './refunds';
export { Users } from './users';
export { Transactions } from './transactions';
export { Invitations } from './invitations';
+export { Verifications } from './verifications';
export { Statistics } from './stats';
export { AnonymousUser } from './anonymousUser';
\ No newline at end of file
diff --git a/webapp/src/services/verifications.ts b/webapp/src/services/verifications.ts
new file mode 100644
index 00000000..33dfa7c4
--- /dev/null
+++ b/webapp/src/services/verifications.ts
@@ -0,0 +1,9 @@
+import { PhoneVerificationRecord } from '../types';
+import axios from 'axios';
+
+export const Verifications = {
+ async verifyPhone(recordId: string) {
+ const res = await axios.post(`/verifications/phone/${recordId}`);
+ return res.data;
+ },
+}
\ No newline at end of file
diff --git a/webapp/src/store/actions.ts b/webapp/src/store/actions.ts
index 1ff8d1a5..c0625bff 100644
--- a/webapp/src/store/actions.ts
+++ b/webapp/src/store/actions.ts
@@ -1,5 +1,5 @@
import { wrapActions, googleSignOut } from './util';
-import { Users, Transactions, Donations, Refunds, Invitations, Statistics } from '../services';
+import { Users, Transactions, Donations, Refunds, Invitations, Verifications, Statistics } from '../services';
import router from '../router';
import { DEFAULT_SIGNED_IN_PAGE, DEFAULT_SIGNED_OUT_PAGE } from '../router/defaults';
import { NominationRole } from '@/types';
@@ -81,6 +81,10 @@ const actions = wrapActions({
commit('updateInvitation', invitation);
commit('setCurrentInvitation', invitation);
},
+ async verifyPhone({ commit }, recordId: string) {
+ const record = await Verifications.verifyPhone(recordId);
+ commit('setPhoneVerificationRecord', record);
+ },
async donate({ commit, state }, { amount }: { amount: number }) {
if (state.user) {
const trx = await Donations.initiateDonation({ amount });
@@ -177,6 +181,7 @@ const actions = wrapActions({
'unsetTransactions',
'unsetInvitations',
'unsetCurrentInvitation',
+ 'unsetPhoneVerificationRecord',
'unsetLastPaymentRequest',
'unsetMessage',
'unsetStats',
diff --git a/webapp/src/store/index.ts b/webapp/src/store/index.ts
index f2e14849..80da8fdd 100644
--- a/webapp/src/store/index.ts
+++ b/webapp/src/store/index.ts
@@ -13,6 +13,7 @@ const state: AppState = {
anonymousUser: undefined,
newUser: undefined,
anonymousDonationDetails: undefined,
+ phoneVerificationRecord: undefined,
beneficiaries: [],
middlemen: [],
transactions: [],
diff --git a/webapp/src/store/mutations.ts b/webapp/src/store/mutations.ts
index 3e435292..9ff60e21 100644
--- a/webapp/src/store/mutations.ts
+++ b/webapp/src/store/mutations.ts
@@ -79,6 +79,12 @@ const mutations: MutationTree = {
unsetCurrentInvitation(state) {
state.currentInvitation = undefined
},
+ setPhoneVerificationRecord(state, record) {
+ state.phoneVerificationRecord = record;
+ },
+ unsetPhoneVerificationRecord(state) {
+ state.phoneVerificationRecord = undefined
+ },
setBeneficiaries(state, beneficiaries) {
state.beneficiaries = beneficiaries
},
diff --git a/webapp/src/types.ts b/webapp/src/types.ts
index 3418d3e1..b03cc981 100644
--- a/webapp/src/types.ts
+++ b/webapp/src/types.ts
@@ -163,10 +163,19 @@ export interface FAQ {
answer: string;
};
+export interface PhoneVerificationRecord {
+ _id: string,
+ phone: string,
+ isVerified: boolean,
+ createdAt: Date,
+ updatedAt: Date,
+}
+
export interface AppState {
user?: User;
anonymousUser?: User;
anonymousDonationDetails?: AnonymousDonateArgs;
+ phoneVerificationRecord?: PhoneVerificationRecord;
newUser?: User;
beneficiaries: User[];
middlemen: User[];
diff --git a/webapp/src/views/verify-phone.vue b/webapp/src/views/verify-phone.vue
new file mode 100644
index 00000000..4468e081
--- /dev/null
+++ b/webapp/src/views/verify-phone.vue
@@ -0,0 +1,50 @@
+
+
+ Your phone number is being verified...
+
+
+ Your phone number {{ phoneVerificationRecord.phone }} has been verified
+
+
+ Your transaction is being verified.
+ Your account will be updated and you will receive a confirmation email when the process is complete.
+
+
+
+ The transaction was cancelled.
+
+
+ Transaction not found
+
+
+ Return Home
+
+
+
+
\ No newline at end of file
From 9733a651f99a3515ce73a69bc9544f9ac8bd86bc Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Thu, 17 Dec 2020 13:44:34 +0200
Subject: [PATCH 14/53] Create a route entry for the verify-phone page
---
webapp/src/app.vue | 1 +
webapp/src/router/index.ts | 5 +++++
webapp/src/views/verify-phone.vue | 12 +-----------
3 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/webapp/src/app.vue b/webapp/src/app.vue
index a5ff7049..28617568 100644
--- a/webapp/src/app.vue
+++ b/webapp/src/app.vue
@@ -42,6 +42,7 @@ export default {
showLoggedInNavigation () {
if (this.$route.name === DEFAULT_SIGNED_OUT_PAGE ||
this.$route.name === 'accept-invitation' ||
+ this.$route.name === 'verify-phone' ||
this.$route.name === 'signup-new-user' ||
(this.$route.name === 'post-payment-flutterwave' && AnonymousUser.isSet())) {
return false
diff --git a/webapp/src/router/index.ts b/webapp/src/router/index.ts
index 04b6547c..48433b66 100644
--- a/webapp/src/router/index.ts
+++ b/webapp/src/router/index.ts
@@ -48,6 +48,11 @@ const routes = [
name: 'accept-invitation',
component: () => import(/* webpackChunkName: "invitations" */ '../views/accept-invitation.vue')
},
+ {
+ path: '/verifications/phone/:id',
+ name: 'verify-phone',
+ component: () => import(/* webpackChunkName: "verifications" */ '../views/verify-phone.vue')
+ },
{
path: '/account',
name: 'account',
diff --git a/webapp/src/views/verify-phone.vue b/webapp/src/views/verify-phone.vue
index 4468e081..7aae4e1e 100644
--- a/webapp/src/views/verify-phone.vue
+++ b/webapp/src/views/verify-phone.vue
@@ -5,20 +5,10 @@
Your phone number {{ phoneVerificationRecord.phone }} has been verified
-
- Your transaction is being verified.
- Your account will be updated and you will receive a confirmation email when the process is complete.
-
-
-
- The transaction was cancelled.
-
-
- Transaction not found
Return Home
-
+
\ No newline at end of file
From 80af8065910f9b170c7a2f7c928ab497626974b7 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 11:56:00 +0200
Subject: [PATCH 32/53] Implement new verifications service method for creating
phone verification records
---
webapp/src/services/verifications.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/webapp/src/services/verifications.ts b/webapp/src/services/verifications.ts
index 5f2eb8d5..2c8e5abd 100644
--- a/webapp/src/services/verifications.ts
+++ b/webapp/src/services/verifications.ts
@@ -2,6 +2,9 @@ import { PhoneVerificationRecord, PhoneVerificationArgs } from '../types';
import axios from 'axios';
export const Verifications = {
+ async createPhoneVerificationRecord(phone: string) {
+ const res = await axios.post(`/verifications/phone`, {phone});
+ },
async verifyPhone(args: PhoneVerificationArgs) {
const { id, code } = args;
const res = await axios.put(`/verifications/phone/${id}`, {code});
From 62e1be6fbdcae39e1fe9ecd4f4b0ca3350205c77 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 12:23:56 +0200
Subject: [PATCH 33/53] Create another a db-specific type that includes field
code in phone verification service docs
---
.../phone-verification-service.ts | 51 +++++++++++++++++--
server/src/core/phone-verification/types.ts | 1 +
server/src/core/user/user-service.ts | 1 -
server/src/rest/routes/verifications.ts | 5 +-
4 files changed, 51 insertions(+), 7 deletions(-)
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 4c0d7626..38e6ee1f 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -22,12 +22,27 @@ const SAFE_PHONE_VERIFICATION_RECORD_PROJECTION = {
updatedAt: 1
};
+function getSafePhoneVerificationRecord(record: DbPhoneVerificationRecord): PhoneVerificationRecord {
+ const recordDict: any = record;
+ return Object.keys(SAFE_PHONE_VERIFICATION_RECORD_PROJECTION)
+ .reduce((safeRecord, field) => {
+ if (field in record) {
+ safeRecord[field] = recordDict[field];
+ }
+
+ return safeRecord;
+ }, {});
+}
+
export interface PhoneVerificationRecord extends VerificationRecord {
+ phone: string,
+}
+
+export interface DbPhoneVerificationRecord extends PhoneVerificationRecord {
/**
* A 6-digit unique code
*/
code: number,
- phone: string,
}
export interface PhoneVerificationArgs {
@@ -85,13 +100,12 @@ export class PhoneVerification implements VerificationService {
}
}
- async create(args: PhoneVerificationRecordCreateArgs): Promise {
- const { id, code, phone } = args;
+ async create(phone: string): Promise {
try {
const now = new Date();
const record = {
- _id: id,
- code,
+ _id: generateId(),
+ code: generatePhoneVerificationCode(),
phone,
isVerified: false,
createdAt: now,
@@ -112,6 +126,33 @@ export class PhoneVerification implements VerificationService {
}
}
+ // async create(args: PhoneVerificationRecordCreateArgs): Promise {
+ // const { id, code, phone } = args;
+ // try {
+ // const now = new Date();
+ // const record = {
+ // _id: id,
+ // code,
+ // phone,
+ // isVerified: false,
+ // createdAt: now,
+ // updatedAt: now,
+ // }
+
+ // const res = await this.collection.insertOne(record);
+ // return res.ops[0];
+ // }
+ // catch (e) {
+ // rethrowIfAppError(e);
+
+ // if (isMongoDuplicateKeyError(e, args.phone)) {
+ // throw createUniquenessFailedError(messages.ERROR_PHONE_ALREADY_IN_USE);
+ // }
+
+ // throw createDbOpFailedError(e.message);
+ // }
+ // }
+
public async getById(id: string): Promise {
try {
const record = await this.collection.findOne(
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
index dc17b4f0..2455fc8e 100644
--- a/server/src/core/phone-verification/types.ts
+++ b/server/src/core/phone-verification/types.ts
@@ -12,4 +12,5 @@ export interface VerificationService {
sendVerificationSms(user: User, id: string, code: number): Promise;
confirmVerificationCode(id: string, code: number): Promise;
getById(id: string): Promise;
+ create(phone: string): Promise;
}
\ No newline at end of file
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index 143c4e55..2d12860c 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -61,7 +61,6 @@ function getSafeUser(user: DbUser): User {
return safeUser;
}, {});
-
}
function hasRole(user: User, role: UserRole): boolean {
diff --git a/server/src/rest/routes/verifications.ts b/server/src/rest/routes/verifications.ts
index 8f884faf..d919148a 100644
--- a/server/src/rest/routes/verifications.ts
+++ b/server/src/rest/routes/verifications.ts
@@ -7,4 +7,7 @@ verifications.put('/phone/:id', wrapResponse(
req => req.core.phoneVerification.confirmVerificationCode(req.params.id, req.body.code)));
verifications.get('/phone/:id', wrapResponse(
- req => req.core.phoneVerification.getById(req.params.id)));
\ No newline at end of file
+ req => req.core.phoneVerification.getById(req.params.id)));
+
+verifications.post('/phone', wrapResponse(
+ req => req.core.phoneVerification.create(req.body.phone)));
\ No newline at end of file
From 6e7b917b3349a7da4afb14cc169ae0d3ac5ef674 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 13:19:38 +0200
Subject: [PATCH 34/53] Implement validators and validation schemas for service
phone-verification
---
.../phone-verification-service.ts | 45 ++-----------------
.../phone-verification/validation-schemas.ts | 6 +++
.../src/core/phone-verification/validator.ts | 8 ++++
3 files changed, 18 insertions(+), 41 deletions(-)
create mode 100644 server/src/core/phone-verification/validation-schemas.ts
create mode 100644 server/src/core/phone-verification/validator.ts
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 38e6ee1f..a3557eea 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -12,6 +12,7 @@ import { createDbOpFailedError, rethrowIfAppError,
import * as messages from '../messages';
import { EventBus, Event } from '../event';
import { UserCreatedEventData, UserActivatedEventData } from '../user';
+import * as validators from './validator';
const COLLECTION = 'phone-verifications';
const SAFE_PHONE_VERIFICATION_RECORD_PROJECTION = {
@@ -22,18 +23,6 @@ const SAFE_PHONE_VERIFICATION_RECORD_PROJECTION = {
updatedAt: 1
};
-function getSafePhoneVerificationRecord(record: DbPhoneVerificationRecord): PhoneVerificationRecord {
- const recordDict: any = record;
- return Object.keys(SAFE_PHONE_VERIFICATION_RECORD_PROJECTION)
- .reduce((safeRecord, field) => {
- if (field in record) {
- safeRecord[field] = recordDict[field];
- }
-
- return safeRecord;
- }, {});
-}
-
export interface PhoneVerificationRecord extends VerificationRecord {
phone: string,
}
@@ -101,9 +90,10 @@ export class PhoneVerification implements VerificationService {
}
async create(phone: string): Promise {
+ validators.validatesCreate(phone);
try {
const now = new Date();
- const record = {
+ const record: DbPhoneVerificationRecord = {
_id: generateId(),
code: generatePhoneVerificationCode(),
phone,
@@ -118,7 +108,7 @@ export class PhoneVerification implements VerificationService {
catch (e) {
rethrowIfAppError(e);
- if (isMongoDuplicateKeyError(e, args.phone)) {
+ if (isMongoDuplicateKeyError(e, phone)) {
throw createUniquenessFailedError(messages.ERROR_PHONE_ALREADY_IN_USE);
}
@@ -126,33 +116,6 @@ export class PhoneVerification implements VerificationService {
}
}
- // async create(args: PhoneVerificationRecordCreateArgs): Promise {
- // const { id, code, phone } = args;
- // try {
- // const now = new Date();
- // const record = {
- // _id: id,
- // code,
- // phone,
- // isVerified: false,
- // createdAt: now,
- // updatedAt: now,
- // }
-
- // const res = await this.collection.insertOne(record);
- // return res.ops[0];
- // }
- // catch (e) {
- // rethrowIfAppError(e);
-
- // if (isMongoDuplicateKeyError(e, args.phone)) {
- // throw createUniquenessFailedError(messages.ERROR_PHONE_ALREADY_IN_USE);
- // }
-
- // throw createDbOpFailedError(e.message);
- // }
- // }
-
public async getById(id: string): Promise {
try {
const record = await this.collection.findOne(
diff --git a/server/src/core/phone-verification/validation-schemas.ts b/server/src/core/phone-verification/validation-schemas.ts
new file mode 100644
index 00000000..1a8a3de7
--- /dev/null
+++ b/server/src/core/phone-verification/validation-schemas.ts
@@ -0,0 +1,6 @@
+import * as joi from '@hapi/joi';
+import { phoneValidationSchema } from '../util/validation-util';
+
+export const createInputSchema = joi.object().keys({
+ phone: phoneValidationSchema,
+});
\ No newline at end of file
diff --git a/server/src/core/phone-verification/validator.ts b/server/src/core/phone-verification/validator.ts
new file mode 100644
index 00000000..3f9fc983
--- /dev/null
+++ b/server/src/core/phone-verification/validator.ts
@@ -0,0 +1,8 @@
+import { createValidationError } from '../error';
+import * as schemas from './validation-schemas';
+
+export const validatesCreate = (phone: string) => {
+ const { error } = schemas.createInputSchema.validate({phone});
+ if (error) throw createValidationError(error.details[0].message);
+ if (phone[0] === '0') createValidationError('Phone number cannot start with 0');
+}
\ No newline at end of file
From ba3e5436c4eadd01b2e89a0fd8340229f5ef12e9 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 13:44:25 +0200
Subject: [PATCH 35/53] Pass user object in body when creating phone
verification record
---
webapp/src/services/verifications.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/webapp/src/services/verifications.ts b/webapp/src/services/verifications.ts
index 2c8e5abd..96e70839 100644
--- a/webapp/src/services/verifications.ts
+++ b/webapp/src/services/verifications.ts
@@ -1,9 +1,9 @@
-import { PhoneVerificationRecord, PhoneVerificationArgs } from '../types';
+import { PhoneVerificationRecord, PhoneVerificationArgs, User } from '../types';
import axios from 'axios';
export const Verifications = {
- async createPhoneVerificationRecord(phone: string) {
- const res = await axios.post(`/verifications/phone`, {phone});
+ async createPhoneVerificationRecord(user: User) {
+ const res = await axios.post(`/verifications/phone`, { user });
},
async verifyPhone(args: PhoneVerificationArgs) {
const { id, code } = args;
From 5ca422cacda56e0fdd5782b66880e695b58d4920 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 14:01:25 +0200
Subject: [PATCH 36/53] Pass user phone in body when creating phone
verification records
---
webapp/src/services/verifications.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/webapp/src/services/verifications.ts b/webapp/src/services/verifications.ts
index 96e70839..ae217b31 100644
--- a/webapp/src/services/verifications.ts
+++ b/webapp/src/services/verifications.ts
@@ -2,8 +2,8 @@ import { PhoneVerificationRecord, PhoneVerificationArgs, User } from '../types';
import axios from 'axios';
export const Verifications = {
- async createPhoneVerificationRecord(user: User) {
- const res = await axios.post(`/verifications/phone`, { user });
+ async createPhoneVerificationRecord(phone: string) {
+ const res = await axios.post(`/verifications/phone`, { phone });
},
async verifyPhone(args: PhoneVerificationArgs) {
const { id, code } = args;
From 6bac9577c1a0b38e3ef8ab36e9f0e678feb18140 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 14:32:02 +0200
Subject: [PATCH 37/53] Remove event emitter emitUserCreated in User service
method create
---
server/src/core/event/event-bus.ts | 5 ---
.../phone-verification-service.ts | 34 +++----------------
.../src/core/phone-verification/validator.ts | 2 +-
server/src/core/user/user-service.ts | 1 -
server/src/rest/routes/verifications.ts | 2 +-
5 files changed, 7 insertions(+), 37 deletions(-)
diff --git a/server/src/core/event/event-bus.ts b/server/src/core/event/event-bus.ts
index 813b2af0..ac41577a 100644
--- a/server/src/core/event/event-bus.ts
+++ b/server/src/core/event/event-bus.ts
@@ -32,11 +32,6 @@ export class EventBus extends EventEmitter {
this.on(EventName.USER_INVITATION_CREATED, listener);
}
- onUserCreated(listener: Listener): void {
- console.log('In onUserCreated...');
- this.on(EventName.USER_CREATED, listener);
- }
-
onUserActivated(listener: Listener): void {
this.on(EventName.USER_ACTIVATED, listener);
}
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index a3557eea..14344fa8 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -58,44 +58,20 @@ export class PhoneVerification implements VerificationService {
this.collection = this.db.collection(COLLECTION);
this.args = args;
this.indexesCreated = false;
-
- this.registerEventHandlers();
- }
-
- private registerEventHandlers() {
- this.args.eventBus.onUserCreated(event => this.handleUserCreated(event));
- this.args.eventBus.onUserActivated(event => this.handleUserActivated(event));
- }
-
- async handleUserCreated(event: Event) {
- return await this.handleUserCreatedOrActivated(event);
}
- async handleUserActivated(event: Event) {
- return await this.handleUserCreatedOrActivated(event);
- }
-
- async handleUserCreatedOrActivated(event: Event) {
- const { data: { user } } = event;
-
+ async create(phone: string): Promise {
+ validators.validatesCreate(phone);
try {
+ const user = await this.args.users.getByPhone(phone);
const id = generateId(); // id to be given to the phone verification record
const code = generatePhoneVerificationCode();
await this.sendVerificationSms(user, id, code);
- const record = await this.create({ id, code, phone: user.phone });
- }
- catch(e) {
- console.error('Error occurred when handling event', event, e);
- }
- }
- async create(phone: string): Promise {
- validators.validatesCreate(phone);
- try {
const now = new Date();
const record: DbPhoneVerificationRecord = {
- _id: generateId(),
- code: generatePhoneVerificationCode(),
+ _id: id,
+ code,
phone,
isVerified: false,
createdAt: now,
diff --git a/server/src/core/phone-verification/validator.ts b/server/src/core/phone-verification/validator.ts
index 3f9fc983..3f5b7455 100644
--- a/server/src/core/phone-verification/validator.ts
+++ b/server/src/core/phone-verification/validator.ts
@@ -2,7 +2,7 @@ import { createValidationError } from '../error';
import * as schemas from './validation-schemas';
export const validatesCreate = (phone: string) => {
- const { error } = schemas.createInputSchema.validate({phone});
+ const { error } = schemas.createInputSchema.validate({ phone });
if (error) throw createValidationError(error.details[0].message);
if (phone[0] === '0') createValidationError('Phone number cannot start with 0');
}
\ No newline at end of file
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index 2d12860c..adb1cbf0 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -162,7 +162,6 @@ export class Users implements UserService {
}
const res = await this.collection.insertOne(user);
- this.eventBus.emitUserCreated({ user: getSafeUser(res.ops[0]) });
return getSafeUser(res.ops[0]);
}
catch (e) {
diff --git a/server/src/rest/routes/verifications.ts b/server/src/rest/routes/verifications.ts
index d919148a..20d0a5c5 100644
--- a/server/src/rest/routes/verifications.ts
+++ b/server/src/rest/routes/verifications.ts
@@ -10,4 +10,4 @@ verifications.get('/phone/:id', wrapResponse(
req => req.core.phoneVerification.getById(req.params.id)));
verifications.post('/phone', wrapResponse(
- req => req.core.phoneVerification.create(req.body.phone)));
\ No newline at end of file
+ req => req.core.phoneVerification.create(req.body.user)));
\ No newline at end of file
From 24de1fc0f9a0be7bc53bdfa6985199efa1a1e9ef Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 14:35:09 +0200
Subject: [PATCH 38/53] Remove event emitter emitUserCreated in User service
method create
---
server/src/core/event/event-bus.ts | 15 +--------------
server/src/core/event/event-name.ts | 4 +---
server/src/core/user/user-service.ts | 1 -
3 files changed, 2 insertions(+), 18 deletions(-)
diff --git a/server/src/core/event/event-bus.ts b/server/src/core/event/event-bus.ts
index ac41577a..2e51d840 100644
--- a/server/src/core/event/event-bus.ts
+++ b/server/src/core/event/event-bus.ts
@@ -1,5 +1,5 @@
import { EventEmitter } from 'events';
-import { UserCreatedEventData, UserActivatedEventData, UserInvitationEventData } from '../user';
+import { UserInvitationEventData } from '../user';
import { TransactionCompletedEventData } from '../payment';
import * as EventName from './event-name';
@@ -32,23 +32,10 @@ export class EventBus extends EventEmitter {
this.on(EventName.USER_INVITATION_CREATED, listener);
}
- onUserActivated(listener: Listener): void {
- this.on(EventName.USER_ACTIVATED, listener);
- }
-
emitTransactionCompleted(eventData: TransactionCompletedEventData): void {
this.innerEmit(EventName.TRANSACTION_COMPLETED, eventData);
}
- emitUserCreated(eventData: UserCreatedEventData): void {
- console.log('In emitUserCreated...');
- this.innerEmit(EventName.USER_CREATED, eventData);
- }
-
- emitUserActivated(eventData: UserActivatedEventData): void {
- this.innerEmit(EventName.USER_ACTIVATED, eventData);
- }
-
onTransactionCompleted(listener: Listener): void {
this.on(EventName.TRANSACTION_COMPLETED, listener);
}
diff --git a/server/src/core/event/event-name.ts b/server/src/core/event/event-name.ts
index 92e99708..11065706 100644
--- a/server/src/core/event/event-name.ts
+++ b/server/src/core/event/event-name.ts
@@ -1,6 +1,4 @@
const USER_INVITATION_CREATED = 'userInvitationCreated';
const TRANSACTION_COMPLETED = 'transactionCompleted';
-const USER_CREATED = 'userCreated';
-const USER_ACTIVATED = 'userActivated';
-export { USER_INVITATION_CREATED, TRANSACTION_COMPLETED, USER_CREATED, USER_ACTIVATED };
+export { USER_INVITATION_CREATED, TRANSACTION_COMPLETED };
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index adb1cbf0..0116069a 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -290,7 +290,6 @@ export class Users implements UserService {
{ upsert: true, returnOriginal: false, projection: NOMINATED_USER_PROJECTION }
);
- this.eventBus.emitUserActivated({ user: getSafeUser(result.value) });
return getSafeUser(result.value);
}
catch (e) {
From 45eda1707328bd17963ffb5aca4663dd0049c33c Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 14:57:14 +0200
Subject: [PATCH 39/53] Redirect user to verify their phone number after
signing up
---
webapp/src/services/verifications.ts | 1 +
webapp/src/store/actions.ts | 8 +++-----
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/webapp/src/services/verifications.ts b/webapp/src/services/verifications.ts
index ae217b31..60df44e0 100644
--- a/webapp/src/services/verifications.ts
+++ b/webapp/src/services/verifications.ts
@@ -4,6 +4,7 @@ import axios from 'axios';
export const Verifications = {
async createPhoneVerificationRecord(phone: string) {
const res = await axios.post(`/verifications/phone`, { phone });
+ return res.data;
},
async verifyPhone(args: PhoneVerificationArgs) {
const { id, code } = args;
diff --git a/webapp/src/store/actions.ts b/webapp/src/store/actions.ts
index 4af1cc6b..02adb36f 100644
--- a/webapp/src/store/actions.ts
+++ b/webapp/src/store/actions.ts
@@ -131,11 +131,9 @@ const actions = wrapActions({
*/
async createUser({ commit }, { name, phone, password, email, googleIdToken }: { name: string; phone: string; password: string; email: string; googleIdToken: string }) {
const user = await Users.createUser({ name, phone, password, email, googleIdToken });
- await Users.login({ phone, password, googleIdToken });
- commit('setUser', user);
-
- if (user) {
- router.push({ name: DEFAULT_SIGNED_IN_PAGE });
+ const record = await Verifications.createPhoneVerificationRecord(user.phone);
+ if (record) {
+ router.push({ path: `/verifications/phone/${record._id}` });
}
},
/**
From 6efa0e21a2bbf6a2d8da30fff06a5e897a83d821 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 15:06:44 +0200
Subject: [PATCH 40/53] Remove event emitter emitUserActivated in User service
method activateMiddleman
---
server/src/core/user/user-service.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/server/src/core/user/user-service.ts b/server/src/core/user/user-service.ts
index 0116069a..54023eac 100644
--- a/server/src/core/user/user-service.ts
+++ b/server/src/core/user/user-service.ts
@@ -333,7 +333,6 @@ export class Users implements UserService {
},
{ upsert: true, returnOriginal: false, projection: NOMINATED_USER_PROJECTION }
);
- this.eventBus.emitUserActivated({ user: getSafeUser(result.value) });
return getSafeUser(result.value);
}
catch (e) {
From 6f6ac310e0d2ae853154ff42dfce29d44013fe50 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 15:14:28 +0200
Subject: [PATCH 41/53] Pass phone to User-service method create upon hitting
endpoint for creating phone verification records
---
server/src/rest/routes/verifications.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/src/rest/routes/verifications.ts b/server/src/rest/routes/verifications.ts
index 20d0a5c5..d919148a 100644
--- a/server/src/rest/routes/verifications.ts
+++ b/server/src/rest/routes/verifications.ts
@@ -10,4 +10,4 @@ verifications.get('/phone/:id', wrapResponse(
req => req.core.phoneVerification.getById(req.params.id)));
verifications.post('/phone', wrapResponse(
- req => req.core.phoneVerification.create(req.body.user)));
\ No newline at end of file
+ req => req.core.phoneVerification.create(req.body.phone)));
\ No newline at end of file
From 1840802f398783ad60bffe6db2eea3eb9e454fed Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 15:40:56 +0200
Subject: [PATCH 42/53] Hide sign-up dialog upon redirection to verify-phone
page
---
webapp/src/components/sign-up-modal.vue | 5 +++++
webapp/src/store/actions.ts | 1 +
webapp/src/views/verify-phone.vue | 2 +-
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/webapp/src/components/sign-up-modal.vue b/webapp/src/components/sign-up-modal.vue
index b909e4b7..a3fc26b3 100644
--- a/webapp/src/components/sign-up-modal.vue
+++ b/webapp/src/components/sign-up-modal.vue
@@ -266,6 +266,11 @@ export default {
role: roles[0]
}
}
+ },
+ phoneVerificationRecord(record) {
+ if (record) {
+ this.hideDialog();
+ }
}
}
}
diff --git a/webapp/src/store/actions.ts b/webapp/src/store/actions.ts
index 02adb36f..c09c6781 100644
--- a/webapp/src/store/actions.ts
+++ b/webapp/src/store/actions.ts
@@ -133,6 +133,7 @@ const actions = wrapActions({
const user = await Users.createUser({ name, phone, password, email, googleIdToken });
const record = await Verifications.createPhoneVerificationRecord(user.phone);
if (record) {
+ commit('setPhoneVerificationRecord', record);
router.push({ path: `/verifications/phone/${record._id}` });
}
},
diff --git a/webapp/src/views/verify-phone.vue b/webapp/src/views/verify-phone.vue
index d7944523..71590bd6 100644
--- a/webapp/src/views/verify-phone.vue
+++ b/webapp/src/views/verify-phone.vue
@@ -8,7 +8,7 @@
- Please enter the 6-digit phone verification code sent to 254...
+ Please enter the 6-digit phone verification code sent to {{phoneVerificationRecord.phone}}
From cf7213c9474ea8f6e0c68dd4bf7cd51c2faa73cc Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 15:43:29 +0200
Subject: [PATCH 43/53] Fetch phone verification record only if not available
in state
---
webapp/src/views/verify-phone.vue | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/webapp/src/views/verify-phone.vue b/webapp/src/views/verify-phone.vue
index 71590bd6..4e4729d9 100644
--- a/webapp/src/views/verify-phone.vue
+++ b/webapp/src/views/verify-phone.vue
@@ -99,7 +99,9 @@ export default {
}
},
async mounted() {
- await this.getPhoneVerificationRecord(this.$route.params.id);
+ if (!this.phoneVerificationRecord) {
+ await this.getPhoneVerificationRecord(this.$route.params.id);
+ }
},
watch: {
async phoneVerificationRecord(record) {
From c74011eefac32cc81eb79a72f96f727be7230257 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 15:51:50 +0200
Subject: [PATCH 44/53] Reformat phone verification sms to be shorter
---
.../src/core/link-generator/link-generator-service.ts | 3 ++-
server/src/core/message/message.ts | 4 ++--
.../phone-verification/phone-verification-service.ts | 10 ++++------
server/src/core/phone-verification/types.ts | 2 +-
4 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/server/src/core/link-generator/link-generator-service.ts b/server/src/core/link-generator/link-generator-service.ts
index ba3d1335..5d3f0065 100644
--- a/server/src/core/link-generator/link-generator-service.ts
+++ b/server/src/core/link-generator/link-generator-service.ts
@@ -59,7 +59,8 @@ export class Links implements LinkGeneratorService {
}
async getPhoneVerificationLink(id: string, shorten: boolean = true): Promise {
- const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
+ // const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
+ const link: string = `https://socialrelief.co/verifications/phone/${id}`;
if (shorten) {
return await this.args.shortener.shortenLink(link);
}
diff --git a/server/src/core/message/message.ts b/server/src/core/message/message.ts
index e21f2bf7..0fc3d53d 100644
--- a/server/src/core/message/message.ts
+++ b/server/src/core/message/message.ts
@@ -51,8 +51,8 @@ export function createMonthlyDistributionReportEmailMessageForOccasionalDonor(do
`;
}
-export function createPhoneVerificationSms(user: User, code: number, verificationLink: string): string {
- return `Hello ${extractFirstName(user.name)}, your phone number verification code is ${code}. Enter this code at ${verificationLink}`
+export function createPhoneVerificationSms(code: number, verificationLink: string): string {
+ return `Social Relief verification code: ${code}.`
}
function beneficiariesAndAmountReceived(beneficiaries: User[], receivedAmount: number[], type: MessageType): string {
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 14344fa8..855bb049 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -11,7 +11,6 @@ import { createDbOpFailedError, rethrowIfAppError,
createUniquenessFailedError } from '../error';
import * as messages from '../messages';
import { EventBus, Event } from '../event';
-import { UserCreatedEventData, UserActivatedEventData } from '../user';
import * as validators from './validator';
const COLLECTION = 'phone-verifications';
@@ -63,10 +62,9 @@ export class PhoneVerification implements VerificationService {
async create(phone: string): Promise {
validators.validatesCreate(phone);
try {
- const user = await this.args.users.getByPhone(phone);
const id = generateId(); // id to be given to the phone verification record
const code = generatePhoneVerificationCode();
- await this.sendVerificationSms(user, id, code);
+ await this.sendVerificationSms(phone, id, code);
const now = new Date();
const record: DbPhoneVerificationRecord = {
@@ -129,11 +127,11 @@ export class PhoneVerification implements VerificationService {
}
}
- async sendVerificationSms(user: User, id: string, code: number): Promise {
+ async sendVerificationSms(phone: string, id: string, code: number): Promise {
try {
const link = await this.args.links.getPhoneVerificationLink(id);
- const smsMessage = createPhoneVerificationSms(user, code, link);
- await this.args.smsProvider.sendSms(user.phone, smsMessage);
+ const smsMessage = createPhoneVerificationSms(code, link);
+ await this.args.smsProvider.sendSms(phone, smsMessage);
this.createIndexes();
}
catch(e) {
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
index 2455fc8e..f8975856 100644
--- a/server/src/core/phone-verification/types.ts
+++ b/server/src/core/phone-verification/types.ts
@@ -9,7 +9,7 @@ export interface VerificationRecord {
export interface VerificationService {
createIndexes(): Promise;
- sendVerificationSms(user: User, id: string, code: number): Promise;
+ sendVerificationSms(phone: string, id: string, code: number): Promise;
confirmVerificationCode(id: string, code: number): Promise;
getById(id: string): Promise;
create(phone: string): Promise;
From 380eb14af6bf85f29b168013c55ae391a6987648 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Mon, 11 Jan 2021 16:19:14 +0200
Subject: [PATCH 45/53] Fetch phone verification record only if not available
in state
---
webapp/src/components/sign-up-modal.vue | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/webapp/src/components/sign-up-modal.vue b/webapp/src/components/sign-up-modal.vue
index a3fc26b3..f0abc5d2 100644
--- a/webapp/src/components/sign-up-modal.vue
+++ b/webapp/src/components/sign-up-modal.vue
@@ -170,7 +170,7 @@ export default {
GoogleButton
},
computed: {
- ...mapState(['user', 'newUser']),
+ ...mapState(['user', 'newUser', 'phoneVerificationRecord']),
imageUrl () {
return require(`@/assets/Social Relief Logo_1.svg`);
},
@@ -267,10 +267,8 @@ export default {
}
}
},
- phoneVerificationRecord(record) {
- if (record) {
- this.hideDialog();
- }
+ phoneVerificationRecord() {
+ this.$bvModal.hide('sign-up');
}
}
}
From 3ce4efa6e149611e0fac967e6ca20a648bb98f88 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 12 Jan 2021 15:50:54 +0200
Subject: [PATCH 46/53] Implement Phone Verification service method to resend
new verification code
---
.../link-generator/link-generator-service.ts | 3 +-
.../phone-verification-service.ts | 40 +++++++++++++++++++
server/src/core/phone-verification/types.ts | 1 +
.../phone-verification/validation-schemas.ts | 30 ++++++++++++++
.../src/core/phone-verification/validator.ts | 10 +++++
5 files changed, 82 insertions(+), 2 deletions(-)
diff --git a/server/src/core/link-generator/link-generator-service.ts b/server/src/core/link-generator/link-generator-service.ts
index 5d3f0065..ba3d1335 100644
--- a/server/src/core/link-generator/link-generator-service.ts
+++ b/server/src/core/link-generator/link-generator-service.ts
@@ -59,8 +59,7 @@ export class Links implements LinkGeneratorService {
}
async getPhoneVerificationLink(id: string, shorten: boolean = true): Promise {
- // const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
- const link: string = `https://socialrelief.co/verifications/phone/${id}`;
+ const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
if (shorten) {
return await this.args.shortener.shortenLink(link);
}
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index 855bb049..bce15210 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -142,6 +142,7 @@ export class PhoneVerification implements VerificationService {
}
public async confirmVerificationCode(id: string, code: number): Promise {
+ validators.validatesConfirmVerificationCode({ recordId: id, code});
try {
const record = await this.collection.findOne(
{ _id: id, code },
@@ -179,4 +180,43 @@ export class PhoneVerification implements VerificationService {
throw createDbOpFailedError(e.message);
}
}
+
+ async resendVerificationCode(id: string): Promise {
+ validators.validatesResendVerificationCode(id);
+ try {
+ const record = await this.collection.findOne(
+ { _id: id, isVerified: false },
+ { projection: SAFE_PHONE_VERIFICATION_RECORD_PROJECTION }
+ );
+
+ if (!record) {
+ throw createPhoneVerificationRecordNotFoundError(messages.ERROR_PHONE_VERIFICATION_RECORD_NOT_FOUND);
+ }
+
+ else if (record.isVerified) {
+ throw createPhoneAlreadyVerifiedError(messages.ERROR_PHONE_ALREADY_VERIFIED);
+ }
+
+ else {
+ const newCode = generatePhoneVerificationCode();
+ await this.sendVerificationSms(record.phone, id, newCode);
+
+ const result = await this.collection.findOneAndUpdate(
+ { _id: id },
+ {
+ $set: { code: newCode },
+ $currentDate: { updatedAt: true },
+ },
+ { upsert: true, returnOriginal: false }
+ );
+
+ return result.value;
+ }
+ }
+ catch(e) {
+ console.error("Error occured: ", e.message);
+ rethrowIfAppError(e);
+ throw createDbOpFailedError(e.message);
+ }
+ }
}
\ No newline at end of file
diff --git a/server/src/core/phone-verification/types.ts b/server/src/core/phone-verification/types.ts
index f8975856..bf5ca79f 100644
--- a/server/src/core/phone-verification/types.ts
+++ b/server/src/core/phone-verification/types.ts
@@ -13,4 +13,5 @@ export interface VerificationService {
confirmVerificationCode(id: string, code: number): Promise;
getById(id: string): Promise;
create(phone: string): Promise;
+ resendVerificationCode(id: string): Promise
}
\ No newline at end of file
diff --git a/server/src/core/phone-verification/validation-schemas.ts b/server/src/core/phone-verification/validation-schemas.ts
index 1a8a3de7..d39242af 100644
--- a/server/src/core/phone-verification/validation-schemas.ts
+++ b/server/src/core/phone-verification/validation-schemas.ts
@@ -1,6 +1,36 @@
import * as joi from '@hapi/joi';
import { phoneValidationSchema } from '../util/validation-util';
+const recordIdSchema = joi.string()
+ .required()
+ .pattern(/^[a-fA-F0-9]{32}$/)
+ .messages({
+ 'any.required': `recordId is required`,
+ 'string.base': 'Invalid type, recordId must be a string',
+ 'string.empty': `Please enter recordId`,
+ 'string.pattern.base': `Invalid recordId. Must contain hexadecimals only and be 32 characters long`
+});
+
+const codeSchema = joi.number()
+ .required()
+ .min(100000)
+ .max(999999)
+ .messages({
+ 'any.required': `Code is required`,
+ 'number.base': 'Invalid type, code must be a number',
+ 'number.min': `Code must be between 100000 and 999999, inclusive`,
+ })
+
+
export const createInputSchema = joi.object().keys({
phone: phoneValidationSchema,
+});
+
+export const confirmVerificationCodeInputSchema = joi.object().keys({
+ recordId: recordIdSchema,
+
+});
+
+export const resendVerificationCodeInputSchema = joi.object().keys({
+ recordId: recordIdSchema
});
\ No newline at end of file
diff --git a/server/src/core/phone-verification/validator.ts b/server/src/core/phone-verification/validator.ts
index 3f5b7455..d8dc4364 100644
--- a/server/src/core/phone-verification/validator.ts
+++ b/server/src/core/phone-verification/validator.ts
@@ -5,4 +5,14 @@ export const validatesCreate = (phone: string) => {
const { error } = schemas.createInputSchema.validate({ phone });
if (error) throw createValidationError(error.details[0].message);
if (phone[0] === '0') createValidationError('Phone number cannot start with 0');
+}
+
+export const validatesConfirmVerificationCode = ({ recordId, code}: { recordId: string, code: number}) => {
+ const { error } = schemas.confirmVerificationCodeInputSchema.validate({ recordId, code });
+ if (error) throw createValidationError(error.details[0].message);
+}
+
+export const validatesResendVerificationCode = (recordId: string) => {
+ const { error } = schemas.resendVerificationCodeInputSchema.validate({ recordId });
+ if (error) throw createValidationError(error.details[0].message);
}
\ No newline at end of file
From ef351aea4501ec06985ad1cb0d85131aa367b6fd Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 12 Jan 2021 16:06:59 +0200
Subject: [PATCH 47/53] Create endpoint for requesting resend of phone
verification code
---
server/src/rest/routes/verifications.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/server/src/rest/routes/verifications.ts b/server/src/rest/routes/verifications.ts
index d919148a..7b3d2151 100644
--- a/server/src/rest/routes/verifications.ts
+++ b/server/src/rest/routes/verifications.ts
@@ -6,6 +6,9 @@ export const verifications = Router();
verifications.put('/phone/:id', wrapResponse(
req => req.core.phoneVerification.confirmVerificationCode(req.params.id, req.body.code)));
+verifications.put('/phone/resend/code/:id', wrapResponse(
+ req => req.core.phoneVerification.resendVerificationCode(req.params.id)));
+
verifications.get('/phone/:id', wrapResponse(
req => req.core.phoneVerification.getById(req.params.id)));
From a6bce31b5943c4cc770bad984ea1b0a5421f9c43 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 12 Jan 2021 16:10:42 +0200
Subject: [PATCH 48/53] Implement action for resending phone verification code
---
webapp/src/services/verifications.ts | 6 +++++-
webapp/src/store/actions.ts | 4 ++++
webapp/src/views/verify-phone.vue | 10 ++++++++--
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/webapp/src/services/verifications.ts b/webapp/src/services/verifications.ts
index 60df44e0..d8d93228 100644
--- a/webapp/src/services/verifications.ts
+++ b/webapp/src/services/verifications.ts
@@ -1,4 +1,4 @@
-import { PhoneVerificationRecord, PhoneVerificationArgs, User } from '../types';
+import { PhoneVerificationRecord, PhoneVerificationArgs } from '../types';
import axios from 'axios';
export const Verifications = {
@@ -15,4 +15,8 @@ export const Verifications = {
const res = await axios.get(`/verifications/phone/${recordId}`);
return res.data;
},
+ async resendPhoneVerificationCode(recordId: string) {
+ const res = await axios.put(`/verifications/phone/resend/code/${recordId}`);
+ return res.data;
+ }
}
\ No newline at end of file
diff --git a/webapp/src/store/actions.ts b/webapp/src/store/actions.ts
index c09c6781..f075b075 100644
--- a/webapp/src/store/actions.ts
+++ b/webapp/src/store/actions.ts
@@ -85,6 +85,10 @@ const actions = wrapActions({
const record = await Verifications.verifyPhone({id, code});
commit('setPhoneVerificationRecord', record);
},
+ async resendPhoneVerificationCode({ commit }, id: string) {
+ const record = await Verifications.resendPhoneVerificationCode(id);
+ commit('setPhoneVerificationRecord', record);
+ },
async getPhoneVerificationRecord({commit}, id: string) {
const record = await Verifications.getPhoneVerificationRecord(id);
commit('setPhoneVerificationRecord', record);
diff --git a/webapp/src/views/verify-phone.vue b/webapp/src/views/verify-phone.vue
index 4e4729d9..a9038470 100644
--- a/webapp/src/views/verify-phone.vue
+++ b/webapp/src/views/verify-phone.vue
@@ -1,6 +1,6 @@
-
+
@@ -82,7 +85,7 @@ export default {
...mapState(['phoneVerificationRecord', 'phoneVerificationErrorMessage']),
},
methods: {
- ...mapActions(['getPhoneVerificationRecord', 'verifyPhone']),
+ ...mapActions(['getPhoneVerificationRecord', 'verifyPhone', 'resendPhoneVerificationCode']),
validateNamedRules,
handleBtnClick() {
store.commit('unsetPhoneVerificationErrorMessage');
@@ -96,6 +99,9 @@ export default {
if (!Object.values(this.validationResults).includes(false)) {
await this.verifyPhone({id: this.phoneVerificationRecord._id, code: Number.parseInt(this.input.code) });
}
+ },
+ async resendCode() {
+ await this.resendPhoneVerificationCode(this.phoneVerificationRecord._id);
}
},
async mounted() {
From 242d6b4a6e6ec3db83382d31663a419a931bfa68 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Tue, 12 Jan 2021 16:36:36 +0200
Subject: [PATCH 49/53] Update confirmVerificationCodeInputSchema to allow code
as input
---
server/src/core/link-generator/link-generator-service.ts | 3 ++-
server/src/core/phone-verification/validation-schemas.ts | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/server/src/core/link-generator/link-generator-service.ts b/server/src/core/link-generator/link-generator-service.ts
index ba3d1335..5d3f0065 100644
--- a/server/src/core/link-generator/link-generator-service.ts
+++ b/server/src/core/link-generator/link-generator-service.ts
@@ -59,7 +59,8 @@ export class Links implements LinkGeneratorService {
}
async getPhoneVerificationLink(id: string, shorten: boolean = true): Promise {
- const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
+ // const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
+ const link: string = `https://socialrelief.co/verifications/phone/${id}`;
if (shorten) {
return await this.args.shortener.shortenLink(link);
}
diff --git a/server/src/core/phone-verification/validation-schemas.ts b/server/src/core/phone-verification/validation-schemas.ts
index d39242af..6e04e061 100644
--- a/server/src/core/phone-verification/validation-schemas.ts
+++ b/server/src/core/phone-verification/validation-schemas.ts
@@ -28,7 +28,7 @@ export const createInputSchema = joi.object().keys({
export const confirmVerificationCodeInputSchema = joi.object().keys({
recordId: recordIdSchema,
-
+ code: codeSchema
});
export const resendVerificationCodeInputSchema = joi.object().keys({
From 34726667b11b3f2452d8a21e9c3978fe5ef1bfca Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Wed, 13 Jan 2021 13:53:39 +0200
Subject: [PATCH 50/53] Throw Invalid phone verification code error if supplied
code does not match with code in corresponding record
---
server/src/core/error.ts | 4 ++++
server/src/core/messages.ts | 1 +
.../phone-verification-service.ts | 19 +++++++++++++------
3 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/server/src/core/error.ts b/server/src/core/error.ts
index d5e72ac0..a32b783a 100644
--- a/server/src/core/error.ts
+++ b/server/src/core/error.ts
@@ -190,4 +190,8 @@ export function createPhoneVerificationRecordNotFoundError(message: string) {
export function createPhoneAlreadyVerifiedError(message: string) {
return createAppError(message, 'phoneAlreadyVerified');
+}
+
+export function createInvalidPhoneVerificationCodeError(message: string) {
+ return createAppError(message, );
}
\ No newline at end of file
diff --git a/server/src/core/messages.ts b/server/src/core/messages.ts
index 22cfba97..2f850dc1 100644
--- a/server/src/core/messages.ts
+++ b/server/src/core/messages.ts
@@ -30,4 +30,5 @@ export const ERROR_NO_BALANCE_FOR_REFUNDS = 'No available balance to request for
export const ERROR_TRANSACTION_REJECTED = 'Transaction rejected';
export const ERROR_PHONE_VERIFICATION_RECORD_NOT_FOUND = 'Phone verification record not found';
export const ERROR_PHONE_ALREADY_VERIFIED = 'Phone already verified';
+export const ERROR_INVALID_PHONE_VERIFICATION_CODE = 'Invalid phone verification code';
diff --git a/server/src/core/phone-verification/phone-verification-service.ts b/server/src/core/phone-verification/phone-verification-service.ts
index bce15210..7c20dcd5 100644
--- a/server/src/core/phone-verification/phone-verification-service.ts
+++ b/server/src/core/phone-verification/phone-verification-service.ts
@@ -1,4 +1,4 @@
-import { Db, Collection, FindAndModifyWriteOpResultObject } from 'mongodb';
+import { Db, Collection } from 'mongodb';
import { generateId, generatePhoneVerificationCode } from '../util';
import { VerificationRecord, VerificationService } from './types';
import { UserService, User } from '../user';
@@ -8,7 +8,7 @@ import { createPhoneVerificationSms } from '../message';
import { createDbOpFailedError, rethrowIfAppError,
createPhoneVerificationRecordNotFoundError,
createPhoneAlreadyVerifiedError, isMongoDuplicateKeyError,
- createUniquenessFailedError } from '../error';
+ createUniquenessFailedError, createInvalidPhoneVerificationCodeError } from '../error';
import * as messages from '../messages';
import { EventBus, Event } from '../event';
import * as validators from './validator';
@@ -144,13 +144,11 @@ export class PhoneVerification implements VerificationService {
public async confirmVerificationCode(id: string, code: number): Promise {
validators.validatesConfirmVerificationCode({ recordId: id, code});
try {
- const record = await this.collection.findOne(
- { _id: id, code },
+ let record = await this.collection.findOne(
+ { _id: id },
{ projection: SAFE_PHONE_VERIFICATION_RECORD_PROJECTION }
);
- console.log('record: ', record);
-
if (!record) {
throw createPhoneVerificationRecordNotFoundError(messages.ERROR_PHONE_VERIFICATION_RECORD_NOT_FOUND);
}
@@ -160,6 +158,15 @@ export class PhoneVerification implements VerificationService {
}
else {
+ record = await this.collection.findOne(
+ { _id: id, code },
+ { projection: SAFE_PHONE_VERIFICATION_RECORD_PROJECTION }
+ );
+
+ if (!record) {
+ throw createInvalidPhoneVerificationCodeError(messages.ERROR_INVALID_PHONE_VERIFICATION_CODE);
+ }
+
const result = await this.collection.findOneAndUpdate(
{ _id: id },
{
From 012c1cb437d33fced9bd4dd03b628483bff5340e Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Wed, 13 Jan 2021 14:13:47 +0200
Subject: [PATCH 51/53] Create an invalidPhoneVerificationCode error code
---
server/src/core/error.ts | 3 ++-
server/src/rest/middleware.ts | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/server/src/core/error.ts b/server/src/core/error.ts
index a32b783a..e4808eb8 100644
--- a/server/src/core/error.ts
+++ b/server/src/core/error.ts
@@ -72,6 +72,7 @@ export type ErrorCode =
| 'emailDeliveryFailed'
| 'linkShorteningFailed'
| 'phoneAlreadyVerified'
+ | 'invalidPhoneVerificationCode'
/**
* This error should only be thrown when a transaction fails
* because the user's transactions are blocked (based on the transactionsBlockedReason field)
@@ -193,5 +194,5 @@ export function createPhoneAlreadyVerifiedError(message: string) {
}
export function createInvalidPhoneVerificationCodeError(message: string) {
- return createAppError(message, );
+ return createAppError(message, 'invalidPhoneVerificationCode');
}
\ No newline at end of file
diff --git a/server/src/rest/middleware.ts b/server/src/rest/middleware.ts
index bdbc0c87..74635e9e 100644
--- a/server/src/rest/middleware.ts
+++ b/server/src/rest/middleware.ts
@@ -32,6 +32,7 @@ export const errorHandler = (): ErrorRequestHandler =>
return sendErrorResponse(res, statusCodes.STATUS_NOT_FOUND, error);
case 'uniquenessFailed':
case 'phoneAlreadyVerified':
+ case 'invalidPhoneVerificationCode':
return sendErrorResponse(res, statusCodes.STATUS_CONFLICT, error);
case 'paymentRequestFailed':
case 'activationFailed':
From 9d6a2d83049294a8234a5c7e70a0eb178f0de583 Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Wed, 13 Jan 2021 15:42:22 +0200
Subject: [PATCH 52/53] Update link in getPhoneVerificationLink
---
server/src/core/link-generator/link-generator-service.ts | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/server/src/core/link-generator/link-generator-service.ts b/server/src/core/link-generator/link-generator-service.ts
index 5d3f0065..ba3d1335 100644
--- a/server/src/core/link-generator/link-generator-service.ts
+++ b/server/src/core/link-generator/link-generator-service.ts
@@ -59,8 +59,7 @@ export class Links implements LinkGeneratorService {
}
async getPhoneVerificationLink(id: string, shorten: boolean = true): Promise {
- // const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
- const link: string = `https://socialrelief.co/verifications/phone/${id}`;
+ const link: string = `${this.args.baseUrl}/verifications/phone/${id}`;
if (shorten) {
return await this.args.shortener.shortenLink(link);
}
From 728e902459d771478f5267d98710a724b3926dbc Mon Sep 17 00:00:00 2001
From: Jason Mahirwe
Date: Fri, 15 Jan 2021 12:02:35 +0200
Subject: [PATCH 53/53] Commit implementation of Verifications service method
resendPhoneVerificationCode
---
webapp/src/services/verifications.ts | 2 +-
webapp/src/store/util.ts | 5 +++++
webapp/src/views/verify-phone.vue | 12 +++++++++---
3 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/webapp/src/services/verifications.ts b/webapp/src/services/verifications.ts
index d8d93228..797ddfba 100644
--- a/webapp/src/services/verifications.ts
+++ b/webapp/src/services/verifications.ts
@@ -3,7 +3,7 @@ import axios from 'axios';
export const Verifications = {
async createPhoneVerificationRecord(phone: string) {
- const res = await axios.post(`/verifications/phone`, { phone });
+ const res = await axios.post(`/verifications/phone`, {phone});
return res.data;
},
async verifyPhone(args: PhoneVerificationArgs) {
diff --git a/webapp/src/store/util.ts b/webapp/src/store/util.ts
index 444ea352..c1067b7d 100644
--- a/webapp/src/store/util.ts
+++ b/webapp/src/store/util.ts
@@ -7,6 +7,7 @@ import Vue from 'vue';
const PHONE_VERIFICATION_RECORD_NOT_FOUND_MESSAGE = 'Phone verification record not found';
const PHONE_ALREADY_VERIFIED_MESSAGE = 'Phone already verified';
+const INVALID_PHONE_VERIFICATION_CODE_MESSAGE = 'Invalid phone verification code';
/* eslint-disable */
function setErrorMessage (e: any, commit: any) {
@@ -23,6 +24,10 @@ function setErrorMessage (e: any, commit: any) {
else if (message === PHONE_ALREADY_VERIFIED_MESSAGE) {
commit('setPhoneVerificationErrorMessage', PHONE_ALREADY_VERIFIED_MESSAGE);
}
+
+ else if (message === INVALID_PHONE_VERIFICATION_CODE_MESSAGE) {
+ commit('setPhoneVerificationErrorMessage', INVALID_PHONE_VERIFICATION_CODE_MESSAGE);
+ }
}
/**
diff --git a/webapp/src/views/verify-phone.vue b/webapp/src/views/verify-phone.vue
index a9038470..a07e4127 100644
--- a/webapp/src/views/verify-phone.vue
+++ b/webapp/src/views/verify-phone.vue
@@ -7,7 +7,10 @@
Phone Verification
-
+
+ Code was successfully resent.
+
+
Please enter the 6-digit phone verification code sent to {{phoneVerificationRecord.phone}}
@@ -69,6 +72,7 @@ export default {
data() {
return {
formSubmitted: false,
+ resendCodeLinkClicked: false,
input: {
code: ''
},
@@ -92,6 +96,7 @@ export default {
this.$router.push({ name: 'home' });
},
async submitCode() {
+ this.resendCodeLinkClicked = false;
this.validationMessages = {
code: validationMessages.code
}
@@ -101,6 +106,7 @@ export default {
}
},
async resendCode() {
+ this.resendCodeLinkClicked = true;
await this.resendPhoneVerificationCode(this.phoneVerificationRecord._id);
}
},
@@ -111,11 +117,11 @@ export default {
},
watch: {
async phoneVerificationRecord(record) {
- if (record && record.isVerified) {
+ if (record && record.isVerified && !this.resendCodeLinkClicked) {
this.validationResults = { code: true };
this.formSubmitted = true;
}
- },
+ }
}
}
\ No newline at end of file