-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sms-limiting): email for disabling sms verification (#2133)
* feat(templates): add html template for email when verification is disabled * feat(mail.utils.ts): added new method for generation of email html * feat(mail.service): new service method to send mail when sms verification disabled * chore(sms-verification-disabled.view): updated wording * test(mail.service.spec): adds tests for verification disabled email sending
- Loading branch information
Showing
6 changed files
with
204 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const SMS_VERIFICATION_LIMIT = 10000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,13 @@ | ||
import ejs from 'ejs' | ||
import { cloneDeep } from 'lodash' | ||
import moment from 'moment-timezone' | ||
import { err, ok, okAsync } from 'neverthrow' | ||
import Mail, { Attachment } from 'nodemailer/lib/mailer' | ||
|
||
import { MailSendError } from 'src/app/services/mail/mail.errors' | ||
import { | ||
MailGenerationError, | ||
MailSendError, | ||
} from 'src/app/services/mail/mail.errors' | ||
import { MailService } from 'src/app/services/mail/mail.service' | ||
import { | ||
AutoreplySummaryRenderData, | ||
|
@@ -14,6 +18,8 @@ import * as MailUtils from 'src/app/services/mail/mail.utils' | |
import { HASH_EXPIRE_AFTER_SECONDS } from 'src/shared/util/verification' | ||
import { BounceType, IPopulatedForm, ISubmissionSchema } from 'src/types' | ||
|
||
import { SMS_VERIFICATION_LIMIT } from '../../../modules/verification/verification.constants' | ||
|
||
const MOCK_VALID_EMAIL = '[email protected]' | ||
const MOCK_VALID_EMAIL_2 = '[email protected]' | ||
const MOCK_VALID_EMAIL_3 = '[email protected]' | ||
|
@@ -1243,4 +1249,109 @@ describe('mail.service', () => { | |
expect(sendMailSpy).toHaveBeenCalledWith(expectedArgs) | ||
}) | ||
}) | ||
|
||
describe('sendSmsVerificationDisabledEmail', () => { | ||
const MOCK_FORM_ID = 'mockFormId' | ||
const MOCK_FORM_TITLE = 'You are all individuals!' | ||
const MOCK_INVALID_EMAIL = 'something wrong@a' | ||
|
||
const MOCK_FORM: IPopulatedForm = { | ||
permissionList: [ | ||
{ email: MOCK_VALID_EMAIL }, | ||
{ email: MOCK_VALID_EMAIL_2 }, | ||
], | ||
admin: { | ||
email: MOCK_VALID_EMAIL_3, | ||
}, | ||
title: MOCK_FORM_TITLE, | ||
_id: MOCK_FORM_ID, | ||
} as unknown as IPopulatedForm | ||
|
||
const MOCK_INVALID_EMAIL_FORM: IPopulatedForm = { | ||
permissionList: [], | ||
admin: { | ||
email: MOCK_INVALID_EMAIL, | ||
}, | ||
title: MOCK_FORM_TITLE, | ||
_id: MOCK_FORM_ID, | ||
} as unknown as IPopulatedForm | ||
|
||
const generateExpectedMailOptions = async ( | ||
count: number, | ||
emailRecipients: string | string[], | ||
) => { | ||
const result = await MailUtils.generateSmsVerificationDisabledHtml({ | ||
formTitle: MOCK_FORM_TITLE, | ||
formLink: `${MOCK_APP_URL}/${MOCK_FORM_ID}`, | ||
smsVerificationLimit: SMS_VERIFICATION_LIMIT, | ||
}).map((emailHtml) => { | ||
return { | ||
to: emailRecipients, | ||
from: MOCK_SENDER_STRING, | ||
html: emailHtml, | ||
subject: '[FormSG] SMS Verification - Free Tier Limit Reached', | ||
replyTo: MOCK_SENDER_EMAIL, | ||
bcc: MOCK_SENDER_EMAIL, | ||
} | ||
}) | ||
return result._unsafeUnwrap() | ||
} | ||
|
||
it('should send verified sms disabled emails successfully', async () => { | ||
// Arrange | ||
// sendMail should return mocked success response | ||
sendMailSpy.mockResolvedValueOnce('mockedSuccessResponse') | ||
|
||
// Act | ||
const actualResult = await mailService.sendSmsVerificationDisabledEmail( | ||
MOCK_FORM, | ||
) | ||
const expectedMailOptions = await generateExpectedMailOptions(1000, [ | ||
MOCK_VALID_EMAIL, | ||
MOCK_VALID_EMAIL_2, | ||
MOCK_VALID_EMAIL_3, | ||
]) | ||
|
||
// Assert | ||
expect(actualResult._unsafeUnwrap()).toEqual(true) | ||
// Check arguments passed to sendNodeMail | ||
expect(sendMailSpy).toHaveBeenCalledTimes(1) | ||
expect(sendMailSpy).toHaveBeenCalledWith(expectedMailOptions) | ||
}) | ||
|
||
it('should return MailSendError when the provided email is invalid', async () => { | ||
// Act | ||
const actualResult = await mailService.sendSmsVerificationDisabledEmail( | ||
MOCK_INVALID_EMAIL_FORM, | ||
) | ||
|
||
// Assert | ||
expect(actualResult).toEqual( | ||
err(new MailSendError('Invalid email error')), | ||
) | ||
// Check arguments passed to sendNodeMail | ||
expect(sendMailSpy).toHaveBeenCalledTimes(0) | ||
}) | ||
|
||
it('should return MailGenerationError when the html template could not be created', async () => { | ||
// Arrange | ||
jest.spyOn(ejs, 'renderFile').mockRejectedValueOnce('no.') | ||
|
||
// Act | ||
const actualResult = await mailService.sendSmsVerificationDisabledEmail( | ||
MOCK_INVALID_EMAIL_FORM, | ||
) | ||
|
||
// Assert | ||
expect(actualResult).toEqual( | ||
err( | ||
new MailGenerationError( | ||
'Error occurred whilst rendering mail template', | ||
), | ||
), | ||
) | ||
// Check arguments passed to sendNodeMail | ||
expect(sendMailSpy).toHaveBeenCalledTimes(0) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
src/app/views/templates/sms-verification-disabled.server.view.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<!DOCTYPE html> | ||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> | ||
<head> </head> | ||
<body> | ||
<p>Dear form admin(s),</p> | ||
<p> | ||
You are receiving this as you have enabled SMS OTP verification on a | ||
Mobile Number field of your form: | ||
<a href="<%= formLink %>"> '<%= formTitle %>' </a>. | ||
</p> | ||
<p> | ||
SMS OTP verification has been <b>automatically disabled</b> on your form | ||
as you have reached the free tier limit of <%= smsVerificationLimit %> | ||
responses. | ||
</p> | ||
|
||
<p> | ||
We would have previously notified all form admins upon your account | ||
reaching 2500, 5000 and 7500 responses while free SMS OTP verification was | ||
enabled. | ||
</p> | ||
<p> | ||
If you require SMS OTP verification for more than <%= smsVerificationLimit | ||
%> responses, please | ||
<a | ||
href="https://guide.form.gov.sg/AdvancedGuide.html#how-do-i-arrange-payment-for-verified-sms" | ||
> | ||
arrange advance billing with us. | ||
</a> | ||
</p> | ||
<p> | ||
<b>Important: </b> Please refrain from creating multiple forms for the | ||
same use case in order to avoid this limit, as such forms risk being | ||
flagged for abuse and blacklisted. | ||
</p> | ||
<p>The FormSG Team</p> | ||
</body> | ||
</html> |