From 1f68ff76c5d1fe6d64431d51bf59a7558c9d0560 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 14 Jan 2025 20:59:12 +1100 Subject: [PATCH 1/5] fix: usernames should encode all characters in emails --- spec/EmailVerificationToken.spec.js | 33 +++++++++++++++++++++++++++++ src/Controllers/UserController.js | 5 +++-- src/Utils.js | 11 ++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 1e9f6a7830..49d4202436 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -52,6 +52,39 @@ describe('Email Verification Token Expiration: ', () => { }); }); + it('should send an HTML or properly escaped plain text password reset email', 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: emailAdapter, + }); + + user.setUsername('hello :)'); + user.setPassword('password123'); + user.set('email', 'user@parse.com'); + await user.signUp(); + + await Parse.User.requestPasswordReset('user@parse.com'); + + expect(sendEmailOptions).not.toBeUndefined(); + + const { link, html, text } = sendEmailOptions; + + const username = link.split('username=')[1]; + expect(username).toBe('%68%65%6C%6C%6F%20%3A%29'); + }); + + 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..ae5f97d891 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 Array.from(input) + .map(char => `%${char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()}`) + .join(''); + } } module.exports = Utils; From 70cf9184b14a77655cc4d599882b368d302940a7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 14 Jan 2025 21:04:52 +1100 Subject: [PATCH 2/5] fix lint --- spec/EmailVerificationToken.spec.js | 18 ++++++++---------- src/Utils.js | 4 ++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 49d4202436..33704531e3 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -62,28 +62,26 @@ describe('Email Verification Token Expiration: ', () => { sendVerificationEmail: async () => {}, sendMail: async () => {}, }; - + await reconfigureServer({ appName: 'specialCharacterUsernameTest', publicServerURL: 'http://localhost:8378/1', emailAdapter: emailAdapter, }); - + user.setUsername('hello :)'); user.setPassword('password123'); user.set('email', 'user@parse.com'); await user.signUp(); - + await Parse.User.requestPasswordReset('user@parse.com'); - - expect(sendEmailOptions).not.toBeUndefined(); - - const { link, html, text } = sendEmailOptions; - - const username = link.split('username=')[1]; + + expect(sendEmailOptions).toBeDefined(); + + const username = sendEmailOptions.link.split('username=')[1]; expect(username).toBe('%68%65%6C%6C%6F%20%3A%29'); }); - + 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(); diff --git a/src/Utils.js b/src/Utils.js index ae5f97d891..f3afc3e282 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -407,8 +407,8 @@ class Utils { */ static encode(input) { return Array.from(input) - .map(char => `%${char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()}`) - .join(''); + .map(char => `%${char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()}`) + .join(''); } } From ac445f716cc6a7fa31c04bcbca0546a40635c4e6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 21 Jan 2025 20:48:54 +1100 Subject: [PATCH 3/5] Update EmailVerificationToken.spec.js --- spec/EmailVerificationToken.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index 33704531e3..f9510bb225 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -71,7 +71,7 @@ describe('Email Verification Token Expiration: ', () => { user.setUsername('hello :)'); user.setPassword('password123'); - user.set('email', 'user@parse.com'); + user.set('email', 'test@example.com'); await user.signUp(); await Parse.User.requestPasswordReset('user@parse.com'); From c2d4821ce0415d46bed9078ddade311050bca30a Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 21 Jan 2025 21:36:18 +1100 Subject: [PATCH 4/5] fix tests --- spec/EmailVerificationToken.spec.js | 2 +- src/Utils.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index f9510bb225..e7022ae568 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -74,7 +74,7 @@ describe('Email Verification Token Expiration: ', () => { user.set('email', 'test@example.com'); await user.signUp(); - await Parse.User.requestPasswordReset('user@parse.com'); + await Parse.User.requestPasswordReset('test@example.com'); expect(sendEmailOptions).toBeDefined(); diff --git a/src/Utils.js b/src/Utils.js index f3afc3e282..64059f2bc9 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -406,9 +406,9 @@ class Utils { * @returns {String} The encoded string. */ static encode(input) { - return Array.from(input) - .map(char => `%${char.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()}`) - .join(''); + return encodeURIComponent(input).replace(/[!'()*]/g, char => + '%' + char.charCodeAt(0).toString(16).toUpperCase() + ); } } From 539ad9f8e4d5c61aaf2503bc9eaef3ee6a122c6f Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 21 Jan 2025 21:42:38 +1100 Subject: [PATCH 5/5] Update EmailVerificationToken.spec.js --- spec/EmailVerificationToken.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/EmailVerificationToken.spec.js b/spec/EmailVerificationToken.spec.js index e7022ae568..acb7dcaf92 100644 --- a/spec/EmailVerificationToken.spec.js +++ b/spec/EmailVerificationToken.spec.js @@ -79,7 +79,7 @@ describe('Email Verification Token Expiration: ', () => { expect(sendEmailOptions).toBeDefined(); const username = sendEmailOptions.link.split('username=')[1]; - expect(username).toBe('%68%65%6C%6C%6F%20%3A%29'); + expect(username).toBe('hello%20%3A%29'); });