Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(frontend): write FieldVerificationService in TypeScript #1259

Merged
merged 17 commits into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/app/modules/verification/verification.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createLoggerWithLabel } from '../../../config/logger'
import { VfnErrors } from '../../../shared/util/verification'

import * as VerificationService from './verification.service'
import { ITransaction } from './verification.types'
import { Transaction } from './verification.types'

const logger = createLoggerWithLabel(module)
/**
Expand All @@ -18,15 +18,15 @@ const logger = createLoggerWithLabel(module)
*/
export const createTransaction: RequestHandler<
Record<string, string>,
ITransaction,
Transaction,
{ formId: string }
> = async (req, res) => {
try {
const { formId } = req.body
const transaction = await VerificationService.createTransaction(formId)
return transaction
? res.status(StatusCodes.CREATED).json(transaction)
: res.sendStatus(StatusCodes.OK)
: res.status(StatusCodes.OK).json({})
} catch (error) {
logger.error({
message: 'Error creating transaction',
Expand Down
4 changes: 2 additions & 2 deletions src/app/modules/verification/verification.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import MailService from '../../services/mail/mail.service'
import { SmsFactory } from '../../services/sms/sms.factory'
import { generateOtp } from '../../utils/otp'

import { ITransaction } from './verification.types'
import { Transaction } from './verification.types'

const Form = getFormModel(mongoose)
const Verification = getVerificationModel(mongoose)
Expand All @@ -38,7 +38,7 @@ const {
*/
export const createTransaction = async (
formId: string,
): Promise<ITransaction | null> => {
): Promise<Transaction | null> => {
const form = await Form.findById(formId)

if (!form) {
Expand Down
10 changes: 6 additions & 4 deletions src/app/modules/verification/verification.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { IVerificationSchema } from '../../../types'

export interface ITransaction {
transactionId: IVerificationSchema['_id']
expireAt: IVerificationSchema['expireAt']
}
export type Transaction =
| {
transactionId: IVerificationSchema['_id']
expireAt: IVerificationSchema['expireAt']
}
| Record<string, never>
1 change: 0 additions & 1 deletion src/public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,6 @@ require('./modules/forms/services/color-themes.client.service.js')
require('./modules/forms/services/form-logic.client.service.js')
require('./modules/forms/services/rating.client.service.js')
require('./modules/forms/services/betas.client.factory.js')
require('./modules/forms/services/verification.client.factory.js')
require('./modules/forms/services/captcha.client.service.js')
require('./modules/forms/services/mailto.client.factory.js')

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'
const { isEmpty, merge, keys } = require('lodash')
const { isEmpty, merge, keys, get } = require('lodash')
const FieldVerificationService = require('../../../../services/FieldVerificationService')
angular.module('forms').component('verifiableFieldComponent', {
transclude: true,
templateUrl: 'modules/forms/base/componentViews/verifiable-field.html',
Expand All @@ -8,16 +9,11 @@ angular.module('forms').component('verifiableFieldComponent', {
field: '<', // The model that the input field is based on
input: '<',
},
controller: [
'Verification',
'$timeout',
'$interval',
verifiableFieldController,
],
controller: ['$q', '$timeout', '$interval', verifiableFieldController],
controllerAs: 'vm',
})

function verifiableFieldController(Verification, $timeout, $interval) {
function verifiableFieldController($q, $timeout, $interval) {
const vm = this
vm.$onInit = () => {
vm.otp = {
Expand Down Expand Up @@ -50,10 +46,11 @@ function verifiableFieldController(Verification, $timeout, $interval) {
throw new Error('No transaction id')
}

await Verification.getNewOtp(
{ transactionId: vm.transactionId },
{ fieldId: vm.field._id, answer: lastRequested.value },
)
await FieldVerificationService.triggerSendOtp({
transactionId: vm.transactionId,
fieldId: vm.field._id,
answer: lastRequested.value,
})
disableResendButton(DISABLED_SECONDS)
updateView(STATES.VFN_WAITING_FOR_INPUT)
} catch (err) {
Expand Down Expand Up @@ -81,9 +78,12 @@ function verifiableFieldController(Verification, $timeout, $interval) {
return onVerificationFailure()
}

await Verification.verifyOtp(
{ transactionId: vm.transactionId },
{ fieldId: vm.field._id, otp: otp },
$q.resolve(
FieldVerificationService.verifyOtp({
transactionId: vm.transactionId,
fieldId: vm.field._id,
otp,
}),
)
.then(onVerificationSuccess)
.catch(onVerificationFailure)
Expand All @@ -110,10 +110,10 @@ function verifiableFieldController(Verification, $timeout, $interval) {
if (getView() !== STATES.VFN_DEFAULT) {
// We don't await on reset because we don't care if it fails
// The signature will be wrong anyway if it fails, and submission will be prevented
Verification.resetFieldInTransaction(
{ transactionId: vm.transactionId },
{ fieldId: vm.field._id },
)
FieldVerificationService.resetVerifiedField({
transactionId: vm.transactionId,
fieldId: vm.field._id,
})
}
resetDefault()
}
Expand Down Expand Up @@ -244,8 +244,11 @@ function verifiableFieldController(Verification, $timeout, $interval) {
}

const getErrorMessage = (err) => {
// So that switch case works for both axios error objects and string objects.
const error = get(err, 'response.data', err)

let errMessage = ''
switch (err) {
switch (error) {
case 'SEND_OTP_FAILED':
case 'RESEND_OTP':
errMessage = 'Error - try resending the OTP.'
Expand Down
20 changes: 11 additions & 9 deletions src/public/modules/forms/base/directives/submit-form.directive.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'
const { cloneDeep } = require('lodash')

const FieldVerificationService = require('../../../../services/FieldVerificationService')
const MyInfoService = require('../../../../services/MyInfoService')
const {
getVisibleFieldIds,
Expand All @@ -23,8 +25,8 @@ const FORM_STATES = {
angular
.module('forms')
.directive('submitFormDirective', [
'$window',
'$q',
'$window',
'GTag',
'SpcpRedirect',
'SpcpSession',
Expand All @@ -34,13 +36,12 @@ angular
'Submissions',
'$uibModal',
'$timeout',
'Verification',
submitFormDirective,
])

function submitFormDirective(
$window,
$q,
$window,
GTag,
SpcpRedirect,
SpcpSession,
Expand All @@ -50,7 +51,6 @@ function submitFormDirective(
Submissions,
$uibModal,
$timeout,
Verification,
) {
return {
restrict: 'E',
Expand Down Expand Up @@ -421,11 +421,13 @@ function submitFormDirective(

// Create a transaction if there are fields to be verified and the form is intended for submission
if (!scope.disableSubmitButton) {
Verification.createTransaction({ formId: scope.form._id }).then(
({ transactionId }) => {
if (transactionId) scope.transactionId = transactionId
},
)
$q.resolve(
FieldVerificationService.createTransactionForForm(scope.form._id),
).then((res) => {
if (res.transactionId) {
scope.transactionId = res.transactionId
}
})
}
},
}
Expand Down
50 changes: 0 additions & 50 deletions src/public/modules/forms/services/verification.client.factory.js

This file was deleted.

100 changes: 100 additions & 0 deletions src/public/services/FieldVerificationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import axios from 'axios'
import { Opaque } from 'type-fest'

export type JsonDate = Opaque<string, 'JsonDate'>
mantariksh marked this conversation as resolved.
Show resolved Hide resolved

/**
* Response when retrieving new transaction. Can be an empty object if the
* current form does not have any verifiable fields.
*/
export type FetchNewTransactionResponse =
| { expireAt: JsonDate; transactionId: string }
| Record<string, never>

type VerifiedFieldSignature = Opaque<string, 'VerifiedFieldSignature'>

/** Exported for testing */
export const TRANSACTION_ENDPOINT = '/transaction'

/**
* Create a transaction for given form.
* @param formId The id of the form to create a transaction for
* @returns transaction metadata on success. Can be empty if no transactions are found in the form.
*/
export const createTransactionForForm = async (
formId: string,
): Promise<FetchNewTransactionResponse> => {
return axios
.post<FetchNewTransactionResponse>(TRANSACTION_ENDPOINT, {
formId,
})
.then(({ data }) => data)
}

/**
* Sends an OTP to given answer.
* @param transactionId The generated transaction id for the form
* @param fieldId The id of the verification field
* @param answer The value of the verification field to verify. Usually an email or phone number
* @returns 201 Created status if successfully sent
*/
export const triggerSendOtp = async ({
transactionId,
fieldId,
answer,
}: {
transactionId: string
fieldId: string
answer: string
}): Promise<void> => {
return axios.post(`${TRANSACTION_ENDPOINT}/${transactionId}/otp`, {
fieldId,
answer,
})
}

/**
* Verifies given OTP for given fieldId
* @param transactionId The generated transaction id for the form
* @param fieldId The id of the verification field
* @param otp The user-entered OTP value to verify against
* @returns The verified signature on success
*/
export const verifyOtp = async ({
transactionId,
fieldId,
otp,
}: {
transactionId: string
fieldId: string
otp: string
}): Promise<VerifiedFieldSignature> => {
return axios
.post<VerifiedFieldSignature>(
`${TRANSACTION_ENDPOINT}/${transactionId}/otp/verify`,
{
fieldId,
otp,
},
)
.then(({ data }) => data)
}

/**
* Reset the field in the transaction, removing the previously saved signature.
*
* @param transactionId The generated transaction id for the form
* @param fieldId The id of the verification field to reset
* @returns 200 OK status if successfully reset
*/
export const resetVerifiedField = async ({
transactionId,
fieldId,
}: {
transactionId: string
fieldId: string
}): Promise<void> => {
return axios.post(`${TRANSACTION_ENDPOINT}/${transactionId}/reset`, {
fieldId,
})
}
Loading