From 9db5d2f7b2d20d5645e09a7a5b40dd6b1759150a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:48:47 +0000 Subject: [PATCH 1/4] Initial plan From 3b672da38087f6c2a00a794f4214efa84fb78ca2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:03:28 +0000 Subject: [PATCH 2/4] Identified root cause of password reset link bug Co-authored-by: mtrezza <5673677+mtrezza@users.noreply.github.com> --- spec/PasswordResetLink.spec.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/PasswordResetLink.spec.js diff --git a/spec/PasswordResetLink.spec.js b/spec/PasswordResetLink.spec.js new file mode 100644 index 0000000000..ec58255642 --- /dev/null +++ b/spec/PasswordResetLink.spec.js @@ -0,0 +1,34 @@ +'use strict'; + +describe('Password Reset Link', () => { + it('should generate a password reset link with actual appId, not {appId}', async () => { + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: options => { + // Check that the link contains the actual appId ('test'), not the literal string '{appId}' + expect(options.link).toBeDefined(); + expect(options.link).not.toContain('{appId}'); + expect(options.link).toContain('/test/'); + console.log('Password reset link:', options.link); + return Promise.resolve(); + }, + sendMail: () => Promise.resolve(), + }; + + await reconfigureServer({ + appId: 'test', + appName: 'Test App', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + + const user = new Parse.User(); + user.setPassword('password'); + user.setUsername('testuser'); + user.set('email', 'test@example.com'); + await user.signUp(); + + await Parse.User.requestPasswordReset('test@example.com'); + }); +}); From 4a8a0255d7d9031714f300408899b3caf4de3aec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:06:30 +0000 Subject: [PATCH 3/4] Fix password reset link to use appId instead of applicationId Co-authored-by: mtrezza <5673677+mtrezza@users.noreply.github.com> --- spec/PasswordResetLink.spec.js | 52 ++++++++++++++++++++++++++++++++-- src/Config.js | 6 ++-- src/Routers/PagesRouter.js | 4 +-- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/spec/PasswordResetLink.spec.js b/spec/PasswordResetLink.spec.js index ec58255642..4cc44c2f63 100644 --- a/spec/PasswordResetLink.spec.js +++ b/spec/PasswordResetLink.spec.js @@ -1,15 +1,17 @@ 'use strict'; describe('Password Reset Link', () => { - it('should generate a password reset link with actual appId, not {appId}', async () => { + it('should generate a password reset link with actual appId, not {appId} or undefined', async () => { + let emailLinkReceived = null; + const emailAdapter = { sendVerificationEmail: () => Promise.resolve(), sendPasswordResetEmail: options => { + emailLinkReceived = options.link; // Check that the link contains the actual appId ('test'), not the literal string '{appId}' expect(options.link).toBeDefined(); expect(options.link).not.toContain('{appId}'); expect(options.link).toContain('/test/'); - console.log('Password reset link:', options.link); return Promise.resolve(); }, sendMail: () => Promise.resolve(), @@ -30,5 +32,51 @@ describe('Password Reset Link', () => { await user.signUp(); await Parse.User.requestPasswordReset('test@example.com'); + + // Verify the link was generated correctly + expect(emailLinkReceived).not.toBeNull(); + expect(emailLinkReceived).toMatch(/\/test\/request_password_reset\?token=/); + }); + + it('should render password reset page with actual appId in form action', async () => { + const emailAdapter = { + sendVerificationEmail: () => Promise.resolve(), + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => Promise.resolve(), + }; + + await reconfigureServer({ + appId: 'test', + appName: 'Test App', + verifyUserEmails: true, + emailAdapter: emailAdapter, + publicServerURL: 'http://localhost:8378/1', + }); + + const user = new Parse.User(); + user.setPassword('password'); + user.setUsername('testuser2'); + user.set('email', 'test2@example.com'); + await user.signUp(); + + // Trigger password reset to get a token + await Parse.User.requestPasswordReset('test2@example.com'); + + // Find the user to get the reset token + const results = await Parse.Query('_User') + .equalTo('email', 'test2@example.com') + .find({ useMasterKey: true }); + const resetToken = results[0].get('_perishable_token'); + + // Request the password reset page + const response = await request({ + url: `http://localhost:8378/1/apps/test/request_password_reset?token=${resetToken}`, + followRedirects: false, + }); + + expect(response.status).toBe(200); + // The form action should contain the actual appId 'test', not '{appId}' + expect(response.text).toContain('/apps/test/request_password_reset'); + expect(response.text).not.toContain('{appId}'); }); }); diff --git a/src/Config.js b/src/Config.js index 54e3cc5ca4..323009ffdd 100644 --- a/src/Config.js +++ b/src/Config.js @@ -49,7 +49,7 @@ export class Config { Object.keys(cacheInfo).forEach(key => { if (key == 'databaseController') { config.database = new DatabaseController(cacheInfo.databaseController.adapter, config); - } else { + } else if (key !== 'applicationId') { config[key] = cacheInfo[key]; } }); @@ -789,7 +789,7 @@ export class Config { } get requestResetPasswordURL() { - return `${this.publicServerURL}/${this.pagesEndpoint}/${this.applicationId}/request_password_reset`; + return `${this.publicServerURL}/${this.pagesEndpoint}/${this.appId}/request_password_reset`; } get passwordResetSuccessURL() { @@ -804,7 +804,7 @@ export class Config { } get verifyEmailURL() { - return `${this.publicServerURL}/${this.pagesEndpoint}/${this.applicationId}/verify_email`; + return `${this.publicServerURL}/${this.pagesEndpoint}/${this.appId}/verify_email`; } async loadMasterKey() { diff --git a/src/Routers/PagesRouter.js b/src/Routers/PagesRouter.js index 74beec770c..906bc62a49 100644 --- a/src/Routers/PagesRouter.js +++ b/src/Routers/PagesRouter.js @@ -160,7 +160,7 @@ export class PagesRouter extends PromiseRouter { () => { const params = { [pageParams.token]: token, - [pageParams.appId]: config.applicationId, + [pageParams.appId]: config.appId, [pageParams.appName]: config.appName, }; return this.goToPage(req, pages.passwordReset, params); @@ -225,7 +225,7 @@ export class PagesRouter extends PromiseRouter { ? {} : { [pageParams.token]: token, - [pageParams.appId]: config.applicationId, + [pageParams.appId]: config.appId, [pageParams.error]: result.err, [pageParams.appName]: config.appName, }; From ab84d653a69e3655787f0f04c95d20d9d77574ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:08:45 +0000 Subject: [PATCH 4/4] Address code review feedback for Parse.Query constructor Co-authored-by: mtrezza <5673677+mtrezza@users.noreply.github.com> --- spec/PasswordResetLink.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/PasswordResetLink.spec.js b/spec/PasswordResetLink.spec.js index 4cc44c2f63..8edee660a8 100644 --- a/spec/PasswordResetLink.spec.js +++ b/spec/PasswordResetLink.spec.js @@ -63,7 +63,7 @@ describe('Password Reset Link', () => { await Parse.User.requestPasswordReset('test2@example.com'); // Find the user to get the reset token - const results = await Parse.Query('_User') + const results = await new Parse.Query('_User') .equalTo('email', 'test2@example.com') .find({ useMasterKey: true }); const resetToken = results[0].get('_perishable_token');