diff --git a/CHANGELOG.md b/CHANGELOG.md index adc1f795..c7edf6d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased + +* The `sendEmail` function can now be passed `santiseContentFor` as an optional argument. + ## 8.3.2 - 2026-03-24 * Include type files in published package (previous attempt did not work) diff --git a/client/notification.js b/client/notification.js index f013ceeb..270014d0 100644 --- a/client/notification.js +++ b/client/notification.js @@ -82,10 +82,11 @@ function NotifyClient(apiKeyOrUrl, serviceIdOrApiKey, apiKeyId) { * @param {String} reference * @param {String} replyToId * @param {String} oneClickUnsubscribeURL + * @param {String[]} [sanitiseContentFor] * * @returns {Object} */ -function createNotificationPayload(type, templateId, to, personalisation, reference, replyToId, oneClickUnsubscribeURL) { +function createNotificationPayload(type, templateId, to, personalisation, reference, replyToId, oneClickUnsubscribeURL, sanitiseContentFor) { var payload = { template_id: templateId @@ -116,6 +117,10 @@ function createNotificationPayload(type, templateId, to, personalisation, refere payload.one_click_unsubscribe_url = oneClickUnsubscribeURL; } + if (sanitiseContentFor && type == 'email') { + payload.sanitise_content_for = sanitiseContentFor; + } + return payload; } @@ -187,22 +192,23 @@ function _check_and_encode_file(file, size_limit) { /** * @param {string} templateId * @param {string} emailAddress - * @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string}} [options] - * @returns {Promise>} + * @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string, sanitiseContentFor?: string[]}} [options] + * @returns {Promise>, uri: string, template: TemplateRef}>>} */ NotifyClient.prototype.sendEmail = function (templateId, emailAddress, options) { options = options || {}; - var err = checkOptionsKeys(['personalisation', 'reference', 'emailReplyToId', 'oneClickUnsubscribeURL'], options) + var err = checkOptionsKeys(['personalisation', 'reference', 'emailReplyToId', 'oneClickUnsubscribeURL', 'sanitiseContentFor'], options) if (err) { return Promise.reject(err); } var personalisation = options.personalisation || undefined, reference = options.reference || undefined, emailReplyToId = options.emailReplyToId || undefined, - oneClickUnsubscribeURL = options.oneClickUnsubscribeURL || undefined; + oneClickUnsubscribeURL = options.oneClickUnsubscribeURL || undefined, + sanitiseContentFor = options.sanitiseContentFor || undefined; return this.apiClient.post('/v2/notifications/email', - createNotificationPayload('email', templateId, emailAddress, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL)); + createNotificationPayload('email', templateId, emailAddress, personalisation, reference, emailReplyToId, oneClickUnsubscribeURL, sanitiseContentFor)); }; /** diff --git a/spec/integration/schemas/v2/POST_notification_email_response.json b/spec/integration/schemas/v2/POST_notification_email_response.json index ad93a2ee..a3084cff 100644 --- a/spec/integration/schemas/v2/POST_notification_email_response.json +++ b/spec/integration/schemas/v2/POST_notification_email_response.json @@ -11,8 +11,15 @@ "scheduled_for": {"oneOf":[ {"$ref": "definitions.json#/datetime"}, {"type": "null"} - ]} + ]}, + "sanitised_content": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { "type": "string" } + } + } }, "additionalProperties": true, - "required": ["id", "content", "uri", "template"] + "required": ["id", "content", "uri", "template", "sanitised_content"] } diff --git a/spec/integration/schemas/v2/definitions.json b/spec/integration/schemas/v2/definitions.json index 63d6686e..e92d4f93 100644 --- a/spec/integration/schemas/v2/definitions.json +++ b/spec/integration/schemas/v2/definitions.json @@ -26,7 +26,8 @@ "body": {"type": "string"}, "from_email": {"type": "string", "format": "email_address"}, "subject": {"type": "string"}, - "one_click_unsubscribe_url": {"type": ["string", "null"], "format": "uri"} + "one_click_unsubscribe_url": {"type": ["string", "null"], "format": "uri"}, + "sanitise_content_for": {"type": "array", "items": {"type": "string"}} }, "required": ["body", "from_email", "subject"] }, diff --git a/spec/integration/test.js b/spec/integration/test.js index 77dfcbd4..fab656cd 100644 --- a/spec/integration/test.js +++ b/spec/integration/test.js @@ -110,6 +110,31 @@ describer('notification api with a live service', function () { }) }); + it('email notification with sanitise_content_for', () => { + const unsafeInput = 'User, [click here](https://evil.link)'; + const expectedSanitised = 'User, \\[click here\\]\\(\\)'; + + let modified_personalisation = { ...personalisation }; + modified_personalisation['name'] = unsafeInput; + + var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'), + options = { + personalisation: modified_personalisation, + reference: clientRef, + sanitiseContentFor: ['name'] + }; + + return notifyClient.sendEmail(emailTemplateId, email, options).then((response) => { + response.status.should.equal(201); + expect(response.data).to.be.jsonSchema(postEmailNotificationResponseJson); + response.data.content.body.should.contain(expectedSanitised); + const field = response.data.sanitised_content.name; + field.sanitised.should.equal(expectedSanitised); + field.unsanitised.should.equal(unsafeInput); + response.data.should.have.property('sanitised_content'); + }); + }); + it('send email notification with document upload', () => { var postEmailNotificationResponseJson = require('./schemas/v2/POST_notification_email_response.json'), options = {personalisation: { name: 'Foo', documents: diff --git a/spec/notification.js b/spec/notification.js index 50d45943..24439e0e 100644 --- a/spec/notification.js +++ b/spec/notification.js @@ -117,6 +117,31 @@ describe('notification api', () => { }); }); + it('should send an email with sanitise content parameter', () => { + + let email = 'dom@example.com', + templateId = '123', + options = { + personalisation: {code: '12345', name: 'John'}, + sanitiseContentFor: ['code'] + }, + data = { + template_id: templateId, + email_address: email, + personalisation: options.personalisation, + sanitise_content_for: options.sanitiseContentFor + }; + + notifyAuthNock + .post('/v2/notifications/email', data) + .reply(200, {hooray: 'bkbbk'}); + + return notifyClient.sendEmail(templateId, email, options) + .then((response) => { + expect(response.status).to.equal(200); + }); + }); + it('should send an email with document upload', () => { let email = 'dom@example.com', templateId = '123', diff --git a/types/client/notification.d.ts b/types/client/notification.d.ts index 43acccc7..baa511cd 100644 --- a/types/client/notification.d.ts +++ b/types/client/notification.d.ts @@ -193,14 +193,15 @@ declare class NotifyClient { /** * @param {string} templateId * @param {string} emailAddress - * @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string}} [options] - * @returns {Promise>} + * @param {{personalisation?: Object, reference?: string, emailReplyToId?: string, oneClickUnsubscribeURL?: string, sanitiseContentFor?: string[]}} [options] + * @returns {Promise>, uri: string, template: TemplateRef}>>} */ sendEmail(templateId: string, emailAddress: string, options?: { personalisation?: any; reference?: string; emailReplyToId?: string; oneClickUnsubscribeURL?: string; + sanitiseContentFor?: string[]; }): Promise>; uri: string; template: TemplateRef; }>>;