diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 1e9f6a7830..a29fc96d1f 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -52,6 +52,38 @@ describe('Email Verification Token Expiration: ', () => { }); }); + it('should send an HTML or properly escaped plain text password reset email with all special ASCII characters', async () => { + const user = new Parse.User(); + let sendEmailOptions; + const emailAdapter = { + sendPasswordResetEmail: (options) => { + sendEmailOptions = options; + }, + sendVerificationEmail: async () => {}, + sendMail: async () => {}, + }; + + await reconfigureServer({ + appName: 'specialCharacterUsernameTest', + publicServerURL: 'http://localhost:8378/1', + emailAdapter, + silent: false, + }); + + const specialCharacters = `!\"'),.:;<>?]^}`; + user.setUsername(specialCharacters); + user.setPassword('password123'); + user.set('email', 'test@example.com'); + await user.signUp(); + + await Parse.User.requestPasswordReset('test@example.com'); + + expect(sendEmailOptions).toBeDefined(); + + const username = sendEmailOptions.link.split('username=')[1]; + expect(username).toBe("%21%22%27%29%2C%2E%3A%3B%3C%3E%3F%5D%5E%7D"); + }); + it('emailVerified should set to false, if the user does not verify their email before the email verify token expires', done => { const user = new Parse.User(); let sendEmailOptions; diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index ac896c51c2..0f8858ab85 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -6,6 +6,7 @@ import rest from '../rest'; import Parse from 'parse/node'; import AccountLockout from '../AccountLockout'; import Config from '../Config'; +import Utils from '../Utils'; var RestQuery = require('../RestQuery'); var Auth = require('../Auth'); @@ -173,7 +174,7 @@ export class UserController extends AdaptableController { if (!shouldSendEmail) { return; } - const username = encodeURIComponent(fetchedUser.username); + const username = Utils.encode(fetchedUser.username); const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config); const options = { @@ -286,7 +287,7 @@ export class UserController extends AdaptableController { user = await this.setPasswordResetToken(email); } const token = encodeURIComponent(user._perishable_token); - const username = encodeURIComponent(user.username); + const username = Utils.encode(user.username); const link = buildEmailLink(this.config.requestResetPasswordURL, username, token, this.config); const options = { diff --git a/src/Utils.js b/src/Utils.js index b77a3d85d7..b20de46677 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -399,6 +399,17 @@ class Utils { } return obj; } + + /** + * Encodes a string to be used in a URL. + * @param {String} input The string to encode. + * @returns {String} The encoded string. + */ + static encode(input) { + return encodeURIComponent(input).replace(/[!'.()*]/g, char => + '%' + char.charCodeAt(0).toString(16).toUpperCase() + ); + } } module.exports = Utils;